• 协议栈代码:LoRaMac-node v4.7.0
  • 参考文档
    • 《RP002-1.0.4 LoRaWAN® Regional Parameters》
    • 《TS001-1.0.4 LoRaWAN® L2 1.0.4 Specification》

一、占空比

1. 区域和占空比

参考《RP002-1.0.4 LoRaWAN® Regional Parameters》1.3 Regional Parameters Summary Tables

有些 Region 是有占空比限制,有些 Region 是没有占空比限制的。

对于动态频点的 Region:

对于固定频点的 Region:

2. 占空比规则

参考《TS001-1.0.4 LoRaWAN® L2 1.0.4 Specification》7 Retransmissions Backoff

3. 占空比代码

占空比代码涉及到两个概念:

  • 时间信用(Time Credit)

    不同的时间段内,都有一个最大可花费的时间信用,每次发送数据都会消耗一笔时间信用,当时间信用不满足下一次发送的花费时,则不允许发送数据。

  • 观察时间(Observation Time)

    当出现当时间信用不满足下一次发送的花费这种情况时,则会计算到达下一个时间段的观察时间,在下一个时间段内所花费的时间信息会清零。

3.1. 占空比参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* src/mac/region/RegionCommon.c */

/* Duty Cycle 1% */
#define BACKOFF_DC_1_HOUR                          100

#define BACKOFF_DUTY_CYCLE_1_HOUR_IN_S             3600
#define BACKOFF_DUTY_CYCLE_10_HOURS_IN_S           (BACKOFF_DUTY_CYCLE_1_HOUR_IN_S + (BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 10))
#define BACKOFF_DUTY_CYCLE_24_HOURS_IN_S           (BACKOFF_DUTY_CYCLE_10_HOURS_IN_S + (BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 24))
#define BACKOFF_24_HOURS_IN_S                      (BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 24)

#ifndef DUTY_CYCLE_TIME_PERIOD
/* Default duty cycle observation time period is 1 hour (3600000 ms) according to ETSI. */
#define DUTY_CYCLE_TIME_PERIOD                     3600000
#endif

#ifndef DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H
/* Time credits for the join backoff algorithm for the 24H period. */
#define DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H    870000
#endif

3.2. 更新占空比最大值

更新时间信用和观察时间:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* src/mac/region/RegionCommon.c */

static uint16_t UpdateTimeCredits(Band_t *band, bool joined, bool dutyCycleEnabled,
                                  bool lastTxIsJoinRequest, SysTime_t elapsedTimeSinceStartup,
                                  TimerTime_t currentTime, TimerTime_t lastBandUpdateTime)
{
    /* 计算当前时间段的占空比 */
    uint16_t dutyCycle = SetMaxTimeCredits(band, joined, elapsedTimeSinceStartup,
                                           dutyCycleEnabled, lastTxIsJoinRequest);
    /* 观察时间默认是 1 小时 */
    TimerTime_t observation = DUTY_CYCLE_TIME_PERIOD;

    /* 如果当前还未入网, 则根据占空比的规则来更新观察时间 */
    if (joined == false) {
        if (elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_1_HOUR_IN_S) {
            observation = BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 1000;
        } else if (elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_10_HOURS_IN_S) {
            observation = (BACKOFF_DUTY_CYCLE_10_HOURS_IN_S * 1000);
        } else {
            observation = (BACKOFF_DUTY_CYCLE_24_HOURS_IN_S * 1000);
        }
    }

    /**
     * 1. 传进来的 lastBandUpdateTime 是当前时间和 band->LastBandUpdateTime 的时间间隔
     *    这里表示时间间隔已经经过了 observation 观察时间, 需要重新更新时间信用
     * 2. 观察时间 observation 和 band->LastMaxCreditAssignTime 不一样
     *    这里表示由于时间段的不同, 需要更新观察时间
     * 3. band->LastBandUpdateTime 表示的第一次更新时间信用和观察时间
     */
    if ((observation <= lastBandUpdateTime) || (band->LastMaxCreditAssignTime != observation) || (band->LastBandUpdateTime == 0)) {
        /* 更新当前 band 的 MaxTimeCredits / TimeCredits / LastMaxCreditAssignTime */
        band->TimeCredits = band->MaxTimeCredits;
        band->LastBandUpdateTime = currentTime;
        band->LastMaxCreditAssignTime = observation;
    }
    return dutyCycle;
}

计算最大的时间信用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* src/mac/region/RegionCommon.c */

static uint16_t SetMaxTimeCredits(Band_t *band, bool joined, SysTime_t elapsedTimeSinceStartup,
                                  bool dutyCycleEnabled, bool lastTxIsJoinRequest)
{
    uint16_t dutyCycle = band->DCycle;
    /* 时间信用最大值默认是 1 小时 */
    TimerTime_t maxCredits = DUTY_CYCLE_TIME_PERIOD;

    dutyCycle = GetDutyCycle(band, joined, elapsedTimeSinceStartup);

    if (joined == false) {
        /* 当前还未入网 */
        if (elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_1_HOUR_IN_S) {
            /* 0 < t < 0+1 */
            maxCredits = DUTY_CYCLE_TIME_PERIOD;
        } else if (elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_10_HOURS_IN_S) {
            /* 0+1 < t < 0+11 */
            maxCredits = DUTY_CYCLE_TIME_PERIOD;
        } else {
            /* 0+11 + (N x 24 hours) < t < 0 + 35 + (N x 24 hours) */
            maxCredits = DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H;
        }
    } else {
        /* 当前已入网 */
        if (dutyCycleEnabled == false) {
            /* 如果关掉了占空比, 则可以任意发送数据, 不受占空比控制 */
            band->TimeCredits = maxCredits;
        }
    }

    /* 获取当前 band 的时间信用 (TimeCredits) 最大值 */
    band->MaxTimeCredits = maxCredits;

    return dutyCycle;
}

static uint16_t GetDutyCycle(Band_t *band, bool joined, SysTime_t elapsedTimeSinceStartup)
{
    /* 实际的占空比取决于 band 的配置参数 DCycle */
    uint16_t dutyCycle = band->DCycle;

    if (joined == false) {
        uint16_t joinDutyCycle = BACKOFF_DC_1_HOUR;

        dutyCycle = MAX(dutyCycle, joinDutyCycle);
    }

    if (dutyCycle == 0) {
        /* 如果是无效的值, 则设为默认值 1, 表示 100.0 % */
        dutyCycle = 1;
    }

    return dutyCycle;
}

3.3. 使用占空比

数据发送前,计算本次发送所花费的时间信用是否满足条件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* src/mac/region/RegionCommon.c */

TimerTime_t RegionCommonUpdateBandTimeOff(bool joined, Band_t *bands,
                                          uint8_t nbBands, bool dutyCycleEnabled,
                                          bool lastTxIsJoinRequest, SysTime_t elapsedTimeSinceStartup,
                                          TimerTime_t expectedTimeOnAir)
{
    /* 默认值是 (uint32_t)~0, 即 0xFFFFFFFF */
    TimerTime_t minTimeToWait = TIMERTIME_T_MAX;
    ...

    /* 检测每一个 band */
    for (uint8_t i = 0; i < nbBands; i++) {
        /* 上一次调用 UpdateTimeCredits 到当前的时间间隔 */
        TimerTime_t elapsedTime = TimerGetElapsedTime(bands[i].LastBandUpdateTime);

        dutyCycle = UpdateTimeCredits(&bands[i], joined, dutyCycleEnabled,
                                      lastTxIsJoinRequest, elapsedTimeSinceStartup,
                                      currentTime, elapsedTime);

        /* 本次发送预计花费的 TimeCredits */
        creditCosts = expectedTimeOnAir * dutyCycle;

        /* 如果本次发送花费的 TimeCredits 还在剩余范围, 则允许在该 band 发送 */
        if ((bands[i].TimeCredits > creditCosts) || ((dutyCycleEnabled == false) && (joined == true))) {
            bands[i].ReadyForTransmission = true;
            validBands++;
        } else {
            bands[i].ReadyForTransmission = false;

            if (bands[i].MaxTimeCredits > creditCosts) {
                TimerTime_t observationTimeDiff = 0;

                /* 当前时间段内的时间信用已用完, 则使用观察时间 LastMaxCreditAssignTime 减去当前的时间间隔 */
                if (bands[i].LastMaxCreditAssignTime >= elapsedTime) {
                    observationTimeDiff = bands[i].LastMaxCreditAssignTime - elapsedTime;
                }
                /* 取较小的值 */
                minTimeToWait = MIN(minTimeToWait, observationTimeDiff);

                validBands++;
            }
        }
    }

    ...
}

数据发送完成后:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* src/mac/region/RegionCommon.c */

void RegionCommonSetBandTxDone(Band_t *band, TimerTime_t lastTxAirTime, bool joined, SysTime_t elapsedTimeSinceStartup)
{
    uint16_t dutyCycle = GetDutyCycle(band, joined, elapsedTimeSinceStartup);

    if (band->TimeCredits > (lastTxAirTime * dutyCycle)) {
        /* 减去当前发送所花费的 TimeCredits */
        band->TimeCredits -= (lastTxAirTime * dutyCycle);
    } else {
        band->TimeCredits = 0;
    }
}

3.4. 结论

  1. 一个 Region 会有一个或多个 Band,对于每一个 Band,都会维护四个参数:

    • TimeCredits

      当前时间段内可用的时间信用。

    • MaxTimeCredits

      当前时间段内最大的时间信用。

    • LastBandUpdateTime

      当前 Band 参数最后更新的时间。

    • LastMaxCreditAssignTime

      当前 Band 的观察时间。

    更新参数的条件:

    1. 上一次记录的 LastBandUpdateTime 到当前的时间间隔已经超出了本次计算得到的观察时间 observation。
    2. 上一次记录的 LastMaxCreditAssignTime 和本次计算得到的观察时间 observation 不一致。
    3. 第一次更新参数。
  2. 每次发送前都会计算本次发送所要花费的时间信用 creditCosts,如果当前可用的时间信用 TimeCredits 不够时:

    • 如果是立即发送的话,则取消本次发送
    • 如果支持延时发送的话,则在下一个时间阶段再重新发送。
  3. 每次发送成功后,都会更新当前可用的时间信用 TimeCredits

  4. 如果当前设备未入网的话,观察时间分为三个阶段,每个阶段的观察时间都不一样:

    • 前 1 小时内
    • 第 1 个小时到第 11 个小时的期间
    • 超过第 11 个小时后

    如果当前设备已经入网,那么观察时间默认是 1 小时。

  5. 如果当前设备未入网的话,最大时间信用分为三个阶段,每个阶段的观察时间都不一样:

    • 前 1 小时内
    • 第 1 个小时到第 11 个小时的期间
    • 超过第 11 个小时后

    如果当前设备已经入网,则最大时间信用默认是 1 小时。如果此时关闭了占空比(dutyCycleEnabled 为 false),那么可用的时间信用也是 1 小时(表示单位时间内可用任意发送)。

  6. 每个 Region 里面的每一个 Band 都已经预设好了占空比。这个占空比主要是用于计算本次发送所要花费的时间信用、发送完成后更新当前可用的时间信用。

二、示例

1. EU868

1.1. 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/* src/mac/region/RegionEU868.h */

#define EU868_MAX_NB_BANDS    6

/* Band = {DutyCycle, TxMaxPower, LastBandUpdateTime, LastMaxCreditAssignTime, TimeCredits, MaxTimeCredits, ReadyForTransmission} */
#define EU868_BAND0    {100 , EU868_MAX_TX_POWER, 0, 0, 0, 0, 0} //  1.0 %
#define EU868_BAND1    {100 , EU868_MAX_TX_POWER, 0, 0, 0, 0, 0} //  1.0 %
#define EU868_BAND2    {1000, EU868_MAX_TX_POWER, 0, 0, 0, 0, 0} //  0.1 %
#define EU868_BAND3    {10  , EU868_MAX_TX_POWER, 0, 0, 0, 0, 0} // 10.0 %
#define EU868_BAND4    {100 , EU868_MAX_TX_POWER, 0, 0, 0, 0, 0} //  1.0 %
#define EU868_BAND5    {1000, EU868_MAX_TX_POWER, 0, 0, 0, 0, 0} //  0.1 %

/* Channel = {Frequency [Hz], RX1 Frequency [Hz], {((DrMax << 4) | DrMin)}, Band} */
#define EU868_LC1    {868100000, 0, {((DR_5 << 4) | DR_0)}, 1}
#define EU868_LC2    {868300000, 0, {((DR_5 << 4) | DR_0)}, 1}
#define EU868_LC3    {868500000, 0, {((DR_5 << 4) | DR_0)}, 1}

#define EU868_JOIN_CHANNELS    (uint16_t)(LC(1) | LC(2) | LC(3))

EU868 有 6 个 Band,每个 Band 的占空比都不一样。

EU868 默认的入网频点是 868.1 / 868.3 / 868.5 MHz,由于这三个频点放在 Band1 里面,那么其占空比是 1%

1.2. 频繁入网受限

由于入网时的占空比是不可以关闭的,那么当频繁入网时,就会出现超出占空比后不再发送的情况。

这里测试时的 DUTY_CYCLE_TIME_PERIOD 为 1800000,即 18s。

  1. 对于前 1 个小时的频繁入网(假如入网不成功),band 1 的最大时间信用 MaxTimeCredits 是 1800000,可用的时间信用 TimeCredits 也为 1800000。
  2. 当准备发送 Join-Request,会计算本次要花费的时间信用 creditCosts,如果 TimeCredits > creditCosts,则允许发送。
  3. 当 Join-Request 发送完成后,会在 TimeCredits 里面扣掉本次发送花费的时间信用。

如此循环,直到 TimeCredits 不满足下一次发送的花费。

测试打印如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
# 第一次入网
## 发送前
cur TimeCredits: 1800000
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 1651700

# 第二次入网
## 发送前
cur TimeCredits: 1651700
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 1503400

# 第三次入网
## 发送前
cur TimeCredits: 1503400
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 1355100

# 第四次入网
## 发送前
cur TimeCredits: 1355100
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 1206800

# 第五次入网
## 发送前
cur TimeCredits: 1206800
tx creditCosts: 148300 
## 发送后
real tx creditCosts: 148300 
TimeCredits left: 1058500

# 第六次入网
## 发送前
cur TimeCredits: 1058500
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 910200

# 第七次入网
## 发送前
cur TimeCredits: 910200
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 761900

# 第八次入网
## 发送前
cur TimeCredits: 761900
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 613600

# 第九次入网
## 发送前
cur TimeCredits: 613600
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 465300

# 第十次入网
## 发送前
cur TimeCredits: 465300
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 317000

# 第十一次入网
## 发送前
cur TimeCredits: 317000
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 168700

# 第十二次入网
## 发送前
cur TimeCredits: 168700
tx creditCosts: 148300
## 发送后
real tx creditCosts: 148300
TimeCredits left: 20400

# 第十三次入网
## 发送前
cur TimeCredits: 20400
tx creditCosts: 148300
# 条件不满足, 不发送

# 第十三次入网
## 发送前
cur TimeCredits: 20400
tx creditCosts: 148300
# 条件不满足, 不发送

# 需要等下一个时间段才可以继续发送入网请求

版权声明

本文为「Zeepunt 日常随笔」的原创文章,遵循 CC BY-NC-ND 4.0 许可协议。允许在署名作者、注明原文链接且不作任何更改的前提下非商业性地分享本文。

原文链接:https://zeepunt.github.io/article/lora/device%E5%8D%A0%E7%A9%BA%E6%AF%94%E6%9C%BA%E5%88%B6/