arduino-lmic icon indicating copy to clipboard operation
arduino-lmic copied to clipboard

AU915 Dwell Time Restriction Regardless of Version Selected (1.0.2 or 1.0.3) (UplinkDwellTime = 1) (TxParamSetupReq)

Open ElectronicallyE opened this issue 5 months ago • 12 comments

Hello,

According to README.md:

This library implements V1.0.3 of the LoRaWAN specification. However, it can also be used with V1.0.2. The only significant change when selecting V1.0.2 is that the US accepted power range in MAC commands is 10 dBm to 30 dBm; whereas in V1.0.3 the accepted range 2 dBm to 30 dBm.

Another notable change between these versions is the implementation of the 400 ms dwell time limitation for JoinReq in AU915. This means that JoinReqs can only be made at a maximum spreading factor of SF10 (DR2) instead of SF12 (DR0):

LoRaWAN 1.0.2 Regional Parameters (Feb 2017)

2.5 Australia 915-928MHz ISM Band 2.5.2 AU915-928 Channel Frequencies Lines 789-782 If using the over-the-air activation procedure, the end-device should broadcast the JoinReq message alternatively on a random 125 kHz channel amongst the 64 channels defined using DR0 and a random 500 kHz channel amongst the 8 channels defined using DR6. The end device should change channel for every transmission.

2.5.3 AU915-928 Data Rate and End-point Output Power encoding Line 785 The TxParamSetupReq MAC command is not implemented by AU915-928 devices.

LoRaWAN 1.0.3 Regional Parameters (July 2018)

2.6 AU915-928MHz ISM Band 2.6.2 AU915-928 Channel Frequencies Lines 855-858 If using the over-the-air activation procedure, the end-device SHALL broadcast the JoinReq message alternatively on a random 125 kHz channel amongst the 64 channels defined using DR2 and a random 500 kHz channel amongst the 8 channels defined using DR6. The end device SHOULD change channel for every transmission.

Lines 861-866 The default JoinReq Data Rate is DR2 (SF10/125KHz), this setting ensures that end-devices are compatible with the 400ms dwell time limitation until the actual dwell time limit is notified to the end-device by the network server via the MAC command TxParamSetupReq.

AU915-928 end-devices MUST consider UplinkDwellTime = 1 during boot stage until reception of the TxParamSetupReq command.

Changing the LoRaWAN version only appears to affect lmic_us915.c:

int8_t LMICus915_pow2dbm(uint8_t mcmd_ladr_p1) {
        if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) >
            ((LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3)
                        ? US915_LinkAdrReq_POW_MAX_1_0_2
                        : US915_LinkAdrReq_POW_MAX_1_0_3))
                return -128;

It appears that UplinkDwellTime is set to 1, regardless of the version selected for AU915. See lorabase_au915.h:

enum {
        // initial value of UplinkDwellTime before TxParamSetupReq received.
        AU915_INITIAL_TxParam_UplinkDwellTime = 1,
        AU915_UPLINK_DWELL_TIME_osticks = sec2osticks(20),
};

Can you please confirm if my understanding is correct, and if not, what I am missing?

Justification For LoRaWAN Regional Parameters Version 1.0.3 (and the associated LoRaWAN Specification 1.0.3) and above, devices which could operate successfully on SF11 or SF12 but not on SF10 are not able to join the network. This restriction exists to comply with international airtime legislation, however in Australia, dwell time restrictions do not exist, therefore I seek to use Regional Parameters Version 1.0.2 (and the associated LoRaWAN Specification 1.0.2).

I discussed this further on The Things Network Forum.

Additional Information I came to discover this difference by reading the following comment in au915.rs of the Chripstack network server repository:

// LoRaWAN < 1.0.3 + < LoRaWAN 1.1.0B does not have dwell-time

ElectronicallyE avatar Jun 18 '25 01:06 ElectronicallyE

Hi, I think you're right. I did not catch this difference. It's not hard to add AU915 V1.03 support by following the US pattern. However, I'm busy with a hot project for the day job and so won't be able to look at this. I could review changes if you submit a PR. Or I can ask one of my colleagues to take a look.

Best regards, --Terry

terrillmoore avatar Jun 18 '25 02:06 terrillmoore

Hi Terry,

Thank you for the quick reply. I must confess that making changes to the code will be a bit of a challenge, so I will have a look, but would appreciate it if you could assign it to someone.

Thanks,

Lachlan

ElectronicallyE avatar Jun 18 '25 02:06 ElectronicallyE

Hi,

I'll check and come back.

Best regards, --Terry

terrillmoore avatar Jun 18 '25 02:06 terrillmoore

Is it as simple as #define LMIC_ENABLE_TxParamSetupReq 0 under #elif defined(CFG_au915) if LoRaWAN Specification is 1.0.2 (using a similar "if" statement to lmic_us915.c) in lorabase.h?

Then, it's a question of how it affects the DR/SF used for the JoinReq. Looking for the variable which defines that...

ElectronicallyE avatar Jun 18 '25 03:06 ElectronicallyE

So I've worked backwards and think I've found where the DR (data rate) is called out:

do_send (ttn-otaa.ino)

V

LMIC_setTxData2 (lmic.c)

V

LMIC_setTxData2_strict (lmic.c)

V

LMIC_setTxData_strict (lmic.c)

V

engineupdate (lmic.c)

V

engineUpdate_inner (lmic.c)

V

LMIC_startJoining (lmic.c)

V

LMICbandplan_initJoinloop (lmic_bandplan_au915.h)

V

LMICau915_initJoinLoop (lmic_au915.h)

V

LMICuslike_initJoinLoop (lmic_us_like.c)

V

// make sure the datarate is set to DR2 per LoRaWAN regional reqts V1.0.2, (shouldn't this be V1.0.3?) // section 2.*.2 LMICcore_setDrJoin(DRCHG_SET, LMICbandplan_getInitialDrJoin()); (lmic_us_like.c)

V

LMICbandplan_getInitialDrJoin (lmic_bandplan_au915.h)

V

#define LMICbandplan_getInitialDrJoin() (LORAWAN_DR2) (lmic_bandplan_au915.h)

V

enum _dr_code_t { (lorabase.h) LORAWAN_DR0 = 0, LORAWAN_DR1, LORAWAN_DR2, // Means it is equal to 2 LORAWAN_DR3, LORAWAN_DR4, LORAWAN_DR5, LORAWAN_DR6, LORAWAN_DR7, LORAWAN_DR8, LORAWAN_DR9, LORAWAN_DR10, LORAWAN_DR11, LORAWAN_DR12, LORAWAN_DR13, LORAWAN_DR14, LORAWAN_DR15, LORAWAN_DR_LENGTH // 16, for sizing arrays. };

V

Also in lmic_bandplan_au915.h: // preconditions for lmic_us_like.h #define LMICuslike_getFirst500kHzDR() (LORAWAN_DR6) #define LMICuslike_getJoin125kHzDR() (LORAWAN_DR2)

This matches up with LoRaWAN 1.0.3 Regional Parameters (July 2018) (see my original post above).

Based upon LoRaWAN 1.0.2 Regional Parameters (Feb 2017), for V1.0.2 this should be:

#define LMICuslike_getFirst500kHzDR() (LORAWAN_DR6) #define LMICuslike_getJoin125kHzDR() (LORAWAN_DR0) #define LMICbandplan_getInitialDrJoin() (LORAWAN_DR0)

I'm hoping this works because lmic_bandplan_us915.h has:

#define LMICuslike_getFirst500kHzDR() (LORAWAN_DR4) #define LMICuslike_getJoin125kHzDR() (LORAWAN_DR0) #define LMICbandplan_getInitialDrJoin() (LORAWAN_DR0)

For extra information...

V1.0.2 states the following for US902-928:

If using the over-the-air activation procedure, it is recommended that the end-device transmit the JoinRequest message alternatively on a random 125 kHz channel amongst the 64 channels defined using DR0 and a random 500 kHz channel amongst the 8 channels defined using DR4.

V1.0.3 states the following for US902-928:

If using the over-the-air activation procedure, the end-device SHALL transmit the Join request message on random 125 kHz channels amongst the 64 125kHz channels defined using DR0 and on 500 kHz channels amongst the 8 500kHz channels defined using DR4. The end-device SHALL change channels for every transmission.

So, the US frequency plan does not have the DR change between versions that the Australian one does.

Interestingly enough, both V1.0.2 and V1.0.3 state the following:

FCC regulation imposes a maximum dwell time of 400ms on uplinks. The TxParamSetupReq MAC command MUST not be implemented by US902-928 devices.

This matches up with lorabase.h:

#elif defined(CFG_us915)  // =========================================

#include "lorabase_us915.h"

// per 2.2.3: not implemented
#define LMIC_ENABLE_TxParamSetupReq	0

Does this mean it is the responsibility of the operator to ensure their transmissions do not exceed the regulatory limits?

Without looking deeper into this, I'm not sure if there are any other changes required. I would appreciate some input on this if so.

ElectronicallyE avatar Jun 18 '25 05:06 ElectronicallyE

I've gone ahead and made the following changes to the DR which complied successfully, but when I changed #define LMIC_ENABLE_TxParamSetupReq 1 to #define LMIC_ENABLE_TxParamSetupReq 0, I get the following errors:

c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c: In function 'LMICau915_getUplinkDwellBit':
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c:77:17: error: 'struct lmic_t' has no member named 'txParam'
         if (LMIC.txParam == 0xFF) {
                 ^
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c:80:21: error: 'struct lmic_t' has no member named 'txParam'
         return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0;
                     ^
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c: In function 'LMICau915_pow2dbm':
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c:118:57: error: 'struct lmic_t' has no member named 'txParam'
                 return ((s1_t)(LMICau915_getMaxEIRP(LMIC.txParam) - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1)));
                                                         ^
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c: In function 'LMICau915_updateTx':
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c:259:47: error: 'struct lmic_t' has no member named 'txParam'
         LMIC.txpow = LMICau915_getMaxEIRP(LMIC.txParam);
                                               ^
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c:276:45: error: 'struct lmic_t' has no member named 'txParam'
         if (LMICau915_getUplinkDwellBit(LMIC.txParam)) {
                                             ^
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c: In function 'LMICau915_initJoinLoop':
c:\Users\Elect\Documents\Arduino\libraries\arduino-lmic-master\src\lmic\lmic_au915.c:319:50: error: 'struct lmic_t' has no member named 'txParam'
         LMIC.adrTxPow = LMICau915_getMaxEIRP(LMIC.txParam); // dBm
                                                  ^
exit status 1

Compilation error: exit status 1

This makes sense because in lmic.h:

#if LMIC_ENABLE_TxParamSetupReq u1_t txParam; // the saved TX param byte. #endif

Quite reasonably, commenting out all functions that refer to txParam just created another set of errors.

ElectronicallyE avatar Jun 18 '25 09:06 ElectronicallyE

I think I've figured it by commenting out sections, but it's pretty late here, so I'll aim to summarise it tomorrow! :)

ElectronicallyE avatar Jun 18 '25 13:06 ElectronicallyE

As mentioned yesterday, the errors are all caused by LMIC.txParam not being defined in lmic_au915.c.

The below code shows why LMIC.txParam is not defined when LMIC_ENABLE_TxParamSetupReq is disabled (0):

static void resetJoinParams(void) {
    // Reset RX1 data rate offset to default (0)
    LMIC.rx1DrOffset = 0;

    // Set downlink data rate and frequency to default values
    LMIC.dn2Dr       = DR_DNW2;
    LMIC.dn2Freq     = FREQ_DNW2;

    // The following line is included ONLY if LMIC_ENABLE_TxParamSetupReq
    // is defined and true at compile time.
    // This means it is NOT checked or executed conditionally at runtime.
#if LMIC_ENABLE_TxParamSetupReq
    LMIC.txParam     = 0xFF;  // Set txParam to default value if feature enabled
#endif
}

Quite reasonably, the following is not included because the TxParamSetupReq MAC command (txParam) from the gateway is not expected and therefore does not need to be processed:

 #if LMIC_ENABLE_TxParamSetupReq
        case MCMD_TxParamSetupReq: {
            uint8_t txParam;
            txParam = opts[oidx+1];

            // we don't allow unrecognized bits to get to txParam.
            txParam &= (MCMD_TxParam_RxDWELL_MASK|
                        MCMD_TxParam_TxDWELL_MASK|
                        MCMD_TxParam_MaxEIRP_MASK);
            LMIC.txParam = txParam;
            response_fit = put_mac_uplink_byte(MCMD_TxParamSetupAns);
            break;
        } /* end case */
#endif // LMIC_ENABLE_TxParamSetupReq

This means that whatever values that could otherwise be adjusted by the TxParamSetupReq command are controlled independently by the device and for AU915 1.0.2 can remain fixed.

Assuming an AU915 compliant configuration on 1.0.2, the TxParamSetupReq byte would contain the following:

  • Bits 7:6 - RFU (reserved for future use) - By default, RFU bits are set to zero
  • Bits 5 - DownlinkDwellTime - 0 (no restriction, even under 1.0.3 and only applies to AS923)
  • Bits 4 - UplinkDwellTime - 0 (no restriction should apply for 1.0.2)
  • Bits 3:0 - MaxEIRP - 13 (30 dBm, which is the "default MaxEIRP" used regardless of AU915 version and if TxParamSetupReq is enabled)

Binary = 0b00001101 Hex = 0x0D

Objective: Replace LMIC.txParam With Fixed Values

I compared the functions and tables in lmic_au915.c (which by default has TxParamSetupReq enabled in lorabase.h) and lmic_us915.c (which has TxParamSetupReq disabled). I was able to find many which shared the same naming convention. I then identified which functions and tables in lmic_au915.c used TxParam, both directly and indirectly. I then worked through each of these figuring out what static values could be applied.

Doing this, I was left with the following functions:

- LMICau915_getUplinkDwellBit() - Commented out as no longer used in any other function due to changes.

  • LMICau915_maxFrameLen(uint8_t dr) - Commented out if (LMICau915_getUplinkDwellBit()) to align with lmic_us915.c, using only the if statement containing reference to CONST_TABLE(u1_t, maxFrameLens_dwell0)[]. This aligns with the "maximum MACPayload size length (M)" when "UplinkDwellTime = 0" and the FOpt control field is absent (see Table 38 in LoRaWAN 1.0.2 Regional Parameters and Table 39 in LoRaWAN 1.0.3 Regional Parameters which are the same under these conditions).
  • LMICau915_getMaxEIRP(uint8_t mcmd_txparam) - Commented out as no longer used in any other due to changes.
  • LMICau915_pow2dbm(uint8_t mcmd_ladr_p1) - Never called by any function (even with txParam), therefore commented out.
  • LMICau915_updateTx(ostime_t txbeg) - Contains LMIC.txPow, set to a fixed 30 dBm and commented out if (LMICau915_getUplinkDwellBit(LMIC.txParam)).
  • LMICau915_initJoinLoop(void) - Contains LMIC.adrTxPow, set to a fixed 30 dBm.

Please see below a link to a spreadsheet of my workings:

Comparsion Between lmic_au915.c and lmic_us915.c.xlsx

Here is my code with these changes applied:

/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions are met:
*  * Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*  * Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*  * Neither the name of the <organization> nor the
*    names of its contributors may be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define LMIC_DR_LEGACY 0

#include "lmic_bandplan.h"

#if defined(CFG_au915)
// ================================================================================
//
// BEG: AU915 related stuff
//

CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
        ILLEGAL_RPS,                            // [-1]
        MAKERPS(SF12, BW125, CR_4_5, 0, 0),     // [0]
        MAKERPS(SF11, BW125, CR_4_5, 0, 0),     // [1]
        MAKERPS(SF10, BW125, CR_4_5, 0, 0),     // [2]
        MAKERPS(SF9 , BW125, CR_4_5, 0, 0),     // [3]
        MAKERPS(SF8 , BW125, CR_4_5, 0, 0),     // [4]
        MAKERPS(SF7 , BW125, CR_4_5, 0, 0),     // [5]
        MAKERPS(SF8 , BW500, CR_4_5, 0, 0),     // [6]
        ILLEGAL_RPS ,                           // [7]
        MAKERPS(SF12, BW500, CR_4_5, 0, 0),     // [8]
        MAKERPS(SF11, BW500, CR_4_5, 0, 0),     // [9]
        MAKERPS(SF10, BW500, CR_4_5, 0, 0),     // [10]
        MAKERPS(SF9 , BW500, CR_4_5, 0, 0),     // [11]
        MAKERPS(SF8 , BW500, CR_4_5, 0, 0),     // [12]
        MAKERPS(SF7 , BW500, CR_4_5, 0, 0),     // [13]
        ILLEGAL_RPS
};

bit_t
LMICau915_validDR(dr_t dr) {
        // use subtract here to avoid overflow
        if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
                return 0;
        return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
}

static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
        59+5,  59+5,  59+5, 123+5, 250+5, 250+5, 250+5, 0,
        61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };

static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
        0,        0,  19+5,  61+5, 133+5, 250+5, 250+5, 0,
        61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };

static bit_t
LMICau915_getUplinkDwellBit() {
        // // if uninitialized, return default.
        // if (LMIC.txParam == 0xFF) {
        //         return AU915_INITIAL_TxParam_UplinkDwellTime;
        // }
        // return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0;
}

uint8_t LMICau915_maxFrameLen(uint8_t dr) {
        // if (LMICau915_getUplinkDwellBit()) {
                if (dr < LENOF_TABLE(maxFrameLens_dwell0))
                        return TABLE_GET_U1(maxFrameLens_dwell0, dr);
                else
                        return 0;
        // } else {
        //         if (dr < LENOF_TABLE(maxFrameLens_dwell1))
        //                 return TABLE_GET_U1(maxFrameLens_dwell1, dr);
        //         else
        //                 return 0;
        // }
}

// from LoRaWAN 5.8: mapping from txParam to MaxEIRP
static CONST_TABLE(s1_t, TXMAXEIRP)[16] = {
	8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36
};

static int8_t LMICau915_getMaxEIRP(uint8_t mcmd_txparam) {
        // // if uninitialized, return default.
	// if (mcmd_txparam == 0xFF)
	// 	return AU915_TX_EIRP_MAX_DBM;
	// else
	// 	return TABLE_GET_S1(
	// 		TXMAXEIRP,
	// 		(mcmd_txparam & MCMD_TxParam_MaxEIRP_MASK) >>
	// 			MCMD_TxParam_MaxEIRP_SHIFT
	// 		);
}

int8_t LMICau915_pow2dbm(uint8_t mcmd_ladr_p1) {
        // if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) == MCMD_LinkADRReq_POW_MASK)
        //         return -128;
        // else    {
        //         return ((s1_t)(LMICau915_getMaxEIRP(LMIC.txParam) - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1)));
        // }
}

static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
        us2osticksRound(128 << 7),  // DR_SF12
        us2osticksRound(128 << 6),  // DR_SF11
        us2osticksRound(128 << 5),  // DR_SF10
        us2osticksRound(128 << 4),  // DR_SF9
        us2osticksRound(128 << 3),  // DR_SF8
        us2osticksRound(128 << 2),  // DR_SF7
        us2osticksRound(128 << 1),  // DR_SF8C
        us2osticksRound(128 << 0),  // ------
        us2osticksRound(128 << 5),  // DR_SF12CR
        us2osticksRound(128 << 4),  // DR_SF11CR
        us2osticksRound(128 << 3),  // DR_SF10CR
        us2osticksRound(128 << 2),  // DR_SF9CR
        us2osticksRound(128 << 1),  // DR_SF8CR
        us2osticksRound(128 << 0),  // DR_SF7CR
};

// get ostime for symbols based on datarate. This is not like us915,
// becuase the times don't match between the upper half and lower half
// of the table.
ostime_t LMICau915_dr2hsym(uint8_t dr) {
        return TABLE_GET_OSTIME(DR2HSYM_osticks, dr);
}



u4_t LMICau915_convFreq(xref2cu1_t ptr) {
        u4_t freq = (os_rlsbf4(ptr - 1) >> 8) * 100;
        if (freq < AU915_FREQ_MIN || freq > AU915_FREQ_MAX)
                freq = 0;
        return freq;
}

///
/// \brief query number of default channels.
///
/// \note
///     For AU, we have no programmable channels; all channels
///     are fixed. Return the total channel count.
///
u1_t LMIC_queryNumDefaultChannels() {
        return 64 + 8;
}


///
/// \brief LMIC_setupChannel for AU915
///
/// \note there are no progammable channels for US915, so this API
///     always returns FALSE.
///
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
        LMIC_API_PARAMETER(chidx);
        LMIC_API_PARAMETER(freq);
        LMIC_API_PARAMETER(drmap);
        LMIC_API_PARAMETER(band);

        return 0; // all channels are hardwired.
}

bit_t LMIC_disableChannel(u1_t channel) {
        bit_t result = 0;
        if (channel < 72) {
                if (ENABLED_CHANNEL(channel)) {
                        result = 1;
                        if (IS_CHANNEL_125khz(channel))
                                LMIC.activeChannels125khz--;
                        else if (IS_CHANNEL_500khz(channel))
                                LMIC.activeChannels500khz--;
                }
                LMIC.channelMap[channel >> 4] &= ~(1 << (channel & 0xF));
        }
        return result;
}

bit_t LMIC_enableChannel(u1_t channel) {
        bit_t result = 0;
        if (channel < 72) {
                if (!ENABLED_CHANNEL(channel)) {
                        result = 1;
                        if (IS_CHANNEL_125khz(channel))
                                LMIC.activeChannels125khz++;
                        else if (IS_CHANNEL_500khz(channel))
                                LMIC.activeChannels500khz++;
                }
                LMIC.channelMap[channel >> 4] |= (1 << (channel & 0xF));
        }
        return result;
}

bit_t LMIC_enableSubBand(u1_t band) {
        ASSERT(band < 8);
        u1_t start = band * 8;
        u1_t end = start + 8;
        bit_t result = 0;

        // enable all eight 125 kHz channels in this subband
        for (int channel = start; channel < end; ++channel)
                result |= LMIC_enableChannel(channel);

        // there's a single 500 kHz channel associated with
        // each group of 8 125 kHz channels. Enable it, too.
        result |= LMIC_enableChannel(64 + band);
        return result;
}

bit_t LMIC_disableSubBand(u1_t band) {
        ASSERT(band < 8);
        u1_t start = band * 8;
        u1_t end = start + 8;
        bit_t result = 0;

        // disable all eight 125 kHz channels in this subband
        for (int channel = start; channel < end; ++channel)
                result |= LMIC_disableChannel(channel);

        // there's a single 500 kHz channel associated with
        // each group of 8 125 kHz channels. Disable it, too.
        result |= LMIC_disableChannel(64 + band);
        return result;
}

bit_t LMIC_selectSubBand(u1_t band) {
        bit_t result = 0;

        ASSERT(band < 8);
        for (int b = 0; b<8; ++b) {
                if (band == b)
                        result |= LMIC_enableSubBand(b);
                else
                        result |= LMIC_disableSubBand(b);
        }
        return result;
}

void LMICau915_updateTx(ostime_t txbeg) {
        u1_t chnl = LMIC.txChnl;
        // LMIC.txpow = LMICau915_getMaxEIRP(LMIC.txParam);
        LMIC.txpow = 30;
        if (chnl < 64) {
                LMIC.freq = AU915_125kHz_UPFBASE + chnl*AU915_125kHz_UPFSTEP;
        } else {
                ASSERT(chnl < 64 + 8);
                LMIC.freq = AU915_500kHz_UPFBASE + (chnl - 64)*AU915_500kHz_UPFSTEP;
        }

        // Update global duty cycle stat and deal with dwell time.
        u4_t dwellDelay;
        u4_t globalDutyDelay;
        dwellDelay = globalDutyDelay = 0;

        if (LMIC.globalDutyRate != 0) {
                ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
                globalDutyDelay = txbeg + (airtime << LMIC.globalDutyRate);
        }
        // if (LMICau915_getUplinkDwellBit(LMIC.txParam)) {
        //         dwellDelay = AU915_UPLINK_DWELL_TIME_osticks;
        // }
        if (dwellDelay > globalDutyDelay) {
                globalDutyDelay = dwellDelay;
        }
        if (globalDutyDelay != 0) {
                LMIC.globalDutyAvail = txbeg + globalDutyDelay;
        }
}

#if !defined(DISABLE_BEACONS)
void LMICau915_setBcnRxParams(void) {
        LMIC.dataLen = 0;
        LMIC.freq = AU915_500kHz_DNFBASE + LMIC.bcnChnl * AU915_500kHz_DNFSTEP;
        LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN), 1), LEN_BCN);
}
#endif // !DISABLE_BEACONS

// set the Rx1 dndr, rps.
void LMICau915_setRx1Params(void) {
        u1_t const txdr = LMIC.dndr;
        u1_t candidateDr;
        LMIC.freq = AU915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * AU915_500kHz_DNFSTEP;
        if ( /* TX datarate */txdr < AU915_DR_SF8C)
                candidateDr = txdr + 8 - LMIC.rx1DrOffset;
        else
                candidateDr = AU915_DR_SF7CR;

        if (candidateDr < LORAWAN_DR8)
                candidateDr = LORAWAN_DR8;
        else if (candidateDr > LORAWAN_DR13)
                candidateDr = LORAWAN_DR13;

        LMIC.dndr = candidateDr;
        LMIC.rps = dndr2rps(LMIC.dndr);
}

void LMICau915_initJoinLoop(void) {
        // LMIC.txParam is set to 0xFF by the central code at init time.
        LMICuslike_initJoinLoop();

        // initialize the adrTxPower.
        // LMIC.adrTxPow = LMICau915_getMaxEIRP(LMIC.txParam); // dBm
         LMIC.adrTxPow = 30; // dBm
}

//
// END: AU915 related stuff
//
// ================================================================================
#endif

This code compiled successfully! Even though some of you may consider this like a "walk in the park", I was quite astonished that I was able to figure this out.

The board was able to successfully send the join request at DR0/SF12 (having already changed lmic_bandplan_au915.h) on The Things Network using LoRaWAN Specification 1.0.2 and RP001 Regional Parameters 1.0.2 revision B (which is the one I have been referencing in all previous posts).

Now there's a few more things that need to be done:

  • Make these changes based upon the version being selected, copying what has been done in lmic_us915.c for LMICus915_maxFrameLen(uint8_t dr).
  • Compare the performance of the board joining the network under this configuration compared to the default configuration. Anecdotally, it seemed to take many more attempts, but that could be because The Things Network was rejecting it due to me overwhelming the network with long airtime JoinReqs. I'll try on my ChripStack network server to verify.

As always, would be more than happy for someone to verify/review my work at this stage.

ElectronicallyE avatar Jun 19 '25 02:06 ElectronicallyE

Good work! I can't review today but will try to take a look this weekend. Meanwhile, @mukeshbharath is also going to try to find time to take a look. (Would welcome community contributions, too.)

Even though some of you may consider this like a "walk in the park", I was quite astonished that I was able to figure this out.

In fact, this is why I started maintaining the LMIC. It's daunting at first, but once you figure out the conventions, it's tractable. Once I gained some confidence, it turned out to be pretty reasonable for regional adjustments, anyway.

Other notes:

  • The join may be slowed down by the channel selection strategy during join. For US915 I added code in V5 that probes all 64 channels during join. The logic randomly probes subbands rather than randomly probing channels, but it still takes on average 5 probes before you try the right channel. Since it does the probes "without replacement", if you miss the join the first cycle, you need to wait 8 more. (I did a Markov chain analysis on all this... without this, it took IIRC 19 probes on average to hit a channel in the relevant subband.) I believe this is for "US-like" regions so will apply to AU915 (and would apply to the 96-channel variant for China).

  • I can't tell from your comments, are you trying to retain 1.0.2 compatibility? At this point, it might be enough to support 1.0.3 correctly.

terrillmoore avatar Jun 19 '25 18:06 terrillmoore

Thank you @terrillmoore for your feedback. It is much appreciated.

Since my last comment, I've done the following:

  • As you pointed out, the example code ttn_otaa.ino does not select the sub-band unlike the other examples such as ttn-otaa-feather-us915.ino. Last night, I added LMIC_selectSubBand(1) before do_send and that seem to resolve the join issue. Without going off on too much of a tangent, shouldn't this be in the example by default as all network servers I'm familiar with only use one sub-band and that this already exists in the other examples?

  • Much of my evening was spent looking into this DR setting issue (comment added) and the reason why I was receiving a downlink for each uplink on the TTN, the latter of which I understand is a server side issue (https://www.thethingsnetwork.org/forum/t/adr-features-recently-introduced-to-tts-march-2022/55662/23 and https://www.thethingsnetwork.org/forum/t/network-keeps-sending-linkadrreq-even-though-adr-is-off/56388/11). Probably looking like a move to Chripstack for further testing is getting closer...

  • I've compared the 1.0.2 and 1.0.3 Regional Parameters to identify any further differences. In addition to the JoinReq DR, UplinkDwellTime, and MaxEIRP, I also need to disable the "CFList".

1.0.2rB

2.5.4 AU915-928 JoinAccept CFList The AU915-928 LoRaWAN does not support the use of the optional CFlist appended to the JoinAccept message. If the CFlist is not empty it is ignored by the end-device.

1.0.3

2.6.4 AU915-928 JoinAccept CFList The AU915-928 LoRaWAN supports the use of the optional CFlist appended to the JoinResp message. If the CFlist is not empty then the CFListType field SHALL contain the value one (0x01) to indicate the CFList contains a series of ChMask fields. The ChMask fields are interpreted as being controlled by a virtual ChMaskCntl that initializes to a value of zero (0) and increments for each ChMask field to a value of four(4). (The first 16 bits controls the channels 1 to 16, ..)

Fortunately, this was an easy change, by switching #define LMICbandplan_hasJoinCFlist() (1) to #define LMICbandplan_hasJoinCFlist() (0) in lmic_us_like.h. This compiled without an issue.

Tasks for This Evening

  • I look to play around with #define LMIC_DEBUG_LEVEL 2 to enable the debug output

  • I've be able to create "if" statements to enable or disable the changes by the version type chosen. I ran out of time to verify that this works yesterday, so I'll try this evening.

  • Add the "if" statement for the #define LMICbandplan_hasJoinCFlist() as I have for the other sections and test it.

  • Share my updated code here.

To Answer Your Question...

I can't tell from your comments, are you trying to retain 1.0.2 compatibility? At this point, it might be enough to support 1.0.3 correctly.

I'm looking to implement 1.0.2 compatibility for AU915. Why is that you might ask? Simply put, I find it silly that LoRaWAN Regional Parameters versions newer than 1.0.2rB require a device using AU915 to send a JoinReq at DR2/SF10, effectively making DR1/SF11 and DR0/SF12 redundant. If a device must join at SF10, there's little reason for SF11 or SF12 to exist. I understand that this is for dwell time restrictions imposed in other countries, but these do not exist in Australia. In other words, why are Australian users being restricted for limitations imposed in other countries when the frequency plan is designed for Australia? A layman might say that I want to be able to use LoRaWAN to its defined limits.

I linked this above, but I sought clarification on this on the TTN forum previously: https://www.thethingsnetwork.org/forum/t/purpose-of-sf12-and-sf11-if-join-accept-is-sf10/71336

You might then ask, why go to this effort of trying to send a JoinReq at DR0 when you can just use ABP on 1.0.3 and above, skipping joins entirely and using SF12 straight away? It sure sounds simpler, but ABP comes with its own issues of frame counter and MAC settings network server resets if the end device is ever rebooted. Ron J explains this quite well in his blog post.

If you think otherwise, happy to hear it!

A Few Questions For You

  • It appears that MCCI have become the custodian of this library. Was that a formal or informal handover?
  • There appears to be several hundred issues listed as "open". Does this reflect the stability or reliability of this library or is more an indication of incorrect configuration by users due to complexity?
  • The MCCI forum (forum.mcci.io does not appear to be online. Is this expected to be resolved?

ElectronicallyE avatar Jun 20 '25 02:06 ElectronicallyE

Hey @terrillmoore!

Another day, another success!

Let me address each of my above points under Tasks for This Evening:

I look to play around with #define LMIC_DEBUG_LEVEL 2 to enable the debug output

Yep, all good with this, worked a treat! I was able to see that the Invalid downlink, window=RX1 was printed in the serial monitor as a result of the issue on The Things Network sending downlinks after each uplink as a result of ADR being disabled on the device (as mentioned earlier). I jumped over to my Chirpstack network server and the same thing was happening, so I added mac_commands_disabled=true in chripstack.toml (https://www.chirpstack.io/docs/chirpstack/configuration.html, https://forum.chirpstack.io/t/option-to-disable-mac-commands/15891/10, https://forum.chirpstack.io/t/what-is-the-difference-between-disable-mac-commands-and-disable-adr/12521) and it fixed it! I acknowledge that isn't anything to do with this issue or code, but just wanted to capture it for future reference.

I've be able to create "if" statements to enable or disable the changes by the version type chosen. I ran out of time to verify that this works yesterday, so I'll try this evening.

Yep, that's done as well! To keep things cleaner, I might try creating a branch and committing the changes to that.

Add the "if" statement for the #define LMICbandplan_hasJoinCFlist() as I have for the other sections and test it.

As above.

Share my updated code here.

As above again.

ElectronicallyE avatar Jun 21 '25 10:06 ElectronicallyE

Pull request added: https://github.com/mcci-catena/arduino-lmic/pull/1011.

I'll assume the conversation will move over there.

The ball is in your court @terrillmoore!

Image

ElectronicallyE avatar Jun 21 '25 11:06 ElectronicallyE