-
第
1
章介绍
本文档描述了
LoRaWAN
网络协议,
是针对电池供电的终端设备
(
不管移动还是固
定
位置
)
进行优化的一套网络协议。<
/p>
LoRaWAN
网络通常采用星型拓扑
结构,由拓扑中的
网关
来转发
终端
p>
与后台
网络服
务器
间的消息。
网关
通过标准
IP
连接来接入
网络服务器
,
而
终端
则通过单跳的
LoRa
或者
FSK
来和一个或多个
网关
通讯。虽然主要传输方式是
终端
上行传输给
p>
网络服
务器
,但所有的传输通常都是双向的
。
终端和网关间的通讯被分散到不同的信道频点和数据速率上
。数据速率的选择需要
权衡距离和消息时长两个因素,
使用不同
数据速率的设备互不影响。
LoRa
的数据速
< br>率范围可以从
0.3kbps
到
50kbps
。
为了最大程度地延长终端的电池寿命和扩大网络
容量,<
/p>
LoRa
网络使用速率自适应
(ADR)
机制来独立管理每个终端的速率和
RF
输出。
虽然每个设备可以在任意信道,任意时间,发送任意数
据,但需要注意遵守如下规
定:
?
终端的每次传输都使用伪随机方式
来改变信道。频率的多变使得系统具有更
强的抗干扰能力。
<
/p>
终端要遵守相应频段和本地区的无线电规定中的发射占空比要求。
终端要遵守相应频段和本地区的无线电规定中的发射时长要求。
?
?
p>
twowinter
注:发射占空比,意思是发射时长占总时长的比
例。按照无线电规定,每
个设备不能疯狂发射霸占信道,总得给别人一点机会。
这份文档主要讲述协议细节,一些基于各地区规定的操作参数,例如发
射占空比和
发射时长等,在另一份文档
[LoRaWAN
地区参数
]
中做具体描述。将这份文档分开,
是为了加入新地区参数时不影响基础的协议规范。
1.1 LoRaWAN Classes
所有的
LoRaWAN
设备都必须至少实现本文档描述的
Class A
功能。
另外也可以实
现本文档中描述的
Class B
和
Class C
及后续将定义的可选功能。不管怎么样,设
备都必须兼容
Class A
。
1.2
文档约定
MAC
命令的格式写作
LinkCheckReq
(
粗斜体
)
,位和位域的格式写
作
FRMPayload
(
粗体
)
,
常量的格式写作
RECEIVE_DELAY1
,
变量的格式写作
N
。
在本文档中,
?
?
?
所有多字节字段的字节序均采用小端模式
EUI
是
8
字节字段,采用小端模式传输
默认所有
RFU
保留位都设为
0
第
2
章
LoRaWAN Classes
类型介绍
LoRa
是由
Semtech
面向长距离、低功耗、低速率应用
而开发的无线调制技术。本
文档中,将
Class A
基础上实现了更多功能的设备称为
“
更高
class
终端
”
。
2.1 LoRaWAN Classes
LoRa
网络包含基础
LoRaWAN
(称之为
Class A
)和可选功能(
Class
B
,
Class
C
)
:
图
N Classes
?
双向传输终端
(Class
A)
:
Class A
的终端在每
次上行后都会紧跟两个短暂的
下行接收窗口,
以此实现双向传输
。
传输时隙是由终端在有传输需要时安排,
附加一定的随机延时
(
即
ALOHA
协议
)
。这种
Class A
p>
操作是最省电的,要
求应用在终端上行传输后的很短时间内进行服务
器的下行传输。服务器在其
他任何时间进行的下行传输都得等终端的下一次上行。
?
划定接收时隙的双向传输终端
(Class
B)
:
Class B
的终端会有
更多的接收时
隙。
除了
Class A
的随机接收窗口,
Class B
设
备还会在指定时间打开别的接
收窗口。为了让终端可以在指定时间打开接收窗口,终端需
要从网关接收时
间同步的信标
Bea
con
。这使得服务器可以知道终端正在监听。
?
最大化接收时隙的双向传输终端
(Class
C)
:
Class C
的终端基本
是一直打开
着接收窗口,
只在发送时短暂关闭。
Class C
的终端会比
Class A
和
Class B
更加耗电,但同时从服务器下发给终端的时延也是最短的。
2.2
文档范围
< br>这份
LoRaWAN
协议还描述了与
Class A
不同的其他
Class
的额外功能。更高
Class
的终端必须满足
Class A
定义的所有功能。
注意:
物理层帧格式,
MAC
帧格式,
以及协议中更高
class
和
Class A
相同的内容
都写在了
Class A
部分,避免内容重复。
第
3
章
PHY
帧格式
LoRa
有上行消息和下行消息。
3.1
上行消息
< br>上行消息是由终端发出,经过一个或多个网关转发给网络服务器。
上行消息使用
LoRa
射频帧的严格模式,消息中含有
PHDR
和
PHDR_CRC
。载
荷有
CRC
校验来保证完整性。
PHDR
,
PHDR_CRC
及载荷
CRC
域都通过射频收发器加入。
上行
PHY:
Preamble
PHDR
PHDR_CRC
PHYPayload
CRC
图
2.
上行
PHY
帧格式
3.2
下行消息
下行消息是由网络服务器发出,经过单个网关转发给单个终端。
下行消息使用射频帧的严格模式,消息中包含
PHDR
和
PHDR_CRC
。
下行
PHY:
Preamble
图
3.
下行
PHY
帧格式
PHDR
PHDR_CRC
PHYPayload
3.3
接收窗口
每个上行传输后终端都要开
两个短的接收窗口。接收窗口开始时间的规定,是以传
输结束时间为参考。
图
4.
终端接收时隙的时序图
3.3.1
第一接收窗口的信道,数据速率和启动。
第一接收窗口
RX1
使用的频率和上行频率有关,
使用的速率和上行速率有关。
< br>RX1
是在上行调制结束后的
RECEIVE_DELAY1
秒打开。
上行和
RX1
时隙下行速率的
关系是按区域
规定,
详细描述在
[LoRaWAN
地
区参数
]
文件中。
默认第一窗口的速率
是和最后一次上行的速率相同。
3.3.2
第二接收窗口的信道,数据速率和启动。
第二接收窗口
RX2
使用一个固定可配置的频率和数据速率,在上行调制结束后的
RECEIVE_DELAY2
秒打开。频率和数据速率可以通过
MAC
命令
(
见第
5
章
)
。默
认的频率和速率是按区域规定,详细描述在
[LoRaWA
N
地区参数
]
文件中。
3.3.3
接收窗口的持续时间
接收窗口的长度
至少要让终端射频收发器有足够的时间来检测到下行的前导码。
3.3.4
接收方在接收窗口期间的处理
如果在
任何一个接收窗口中检测到前导码,射频收发器需要继续激活,直到整个下
行帧都解调完
毕。如果在第一接收窗口检测到数据帧,且这个数据帧的地址和
MIC
< br>校验通过确认是给这个终端,那终端就不必开启第二个接收窗口。
3.3.5
网络发送消息给终端
<
/p>
如果网络想要发一个下行消息给终端,它会精确地在两个接收窗口的起始点发起传
输。
3.3.6
接收窗口的重要事项
终端在第一或第
二接收窗口收到下行消息后,或者在第二接收窗口阶段,不能再发
起另一个上行消息。<
/p>
3.3.7
其他协议的收发处理
节点在
LoRaWAN
收发窗口阶段可以收发其他协议,只要终端能满足当地要
求以及
兼容
LoRaWAN
协议。
p>
2
梳理解析
LoRaWAN
第
3
< br>章,主要是讲了接收窗口这回事,只要记住张图就行。
目前
RX1
一般是在上行后
1
秒开始,
RX2
是在上
行后
2
秒开始。
3
源码分析
3.1
源码流程
< br>在梳理这章节的对应代码时,自己手动做了张思维导图。有时是这样,代码再有层
次感,也不及一个图。好,请收下。
3.2
发送完成就开始
RX1
和
RX2
延时
static void OnRadioTxDone( void )
{
...
//
Setup timers
if( IsRxWindowsEnabled ==
true )
{
TimerSetValue(&RxWindowTimer1,
RxWindow1Delay );
TimerStart(&RxWindowTimer1 );
if( LoRaMacDeviceClass != CLASS_C )
{
TimerSetValue(&RxWindowTimer2,
RxWindow2Delay );
TimerStart(&RxWindowTimer2 );
}
if( (
LoRaMacDeviceClass == CLASS_C ) || (
NodeAckRequested
== true ) )
{
TimerSetValue(&AckTimeoutTimer,
RxWindow2Delay + ACK_TIMEO
UT +
randr( -ACK_TIMEOUT_RND,
ACK_TIMEOUT_RND ) );
TimerStart(&AckTimeoutTimer );
}
}
...
}
3.3
接收窗口的射频处理
从上面一步,<
/p>
我们已经清晰的知道,
对应的处理肯定是在
OnRxWindow1TimerEvent
和
OnRxW
indow2TimerEvent
中。
< br>这两个接收窗口的处理,会对速率和信道进行设置,按照
LoRaWAN
协议中文版
_
配套文件地区参数
(
物理层
)
中对各地区的要求分别进行处理。
比
如这个
470
的处理,对上行信道对
4
8
取余得到下行信道。
RxWindowSetup(
LORAMAC_FIRST_RX1_CHANNEL + ( Channel % 48
)
* LORAMAC_STEPWIDTH_
第
4
章
MAC
帧格式
LoRa
所有上下行链路消息都会携带
PHY
载荷,
PHY
载荷以
1
字节
MAC
头
(MH
DR)
开始,紧接着
MAC
载荷
(MACPayload)
,最后是
4
字节的
MAC
校验码
(MIC)
。
射频
< br>PHY
层:
Preamble
PHDR
PHDR_CRC
PHYPayload
CRC
图<
/p>
5.
射频
PHY
结构
(
注意
CRC
只有上行链路消息中存在
)
PHY
载荷:
MHDR
或者
MHDR
或者
MHDR
图
载荷结构
MAC
载荷:
FHDR
图
载荷结构
FHDR
:
DevAddr
图
8.
帧头结构
图
帧格式元素
(
即图
5~8)
FCtrl
FCnt
FOpts
FPort
FRMPayload
Join-Response
MIC
Join-Request
MIC
MACPayload
MIC
4.1
MAC
层
(PHYPayload)
Size (bytes)
PHYPayload
1
MHDR
1..M
MACPayload
4
MIC
MACPayload
字段的最大长度
M
,在第
6
章有详细说明。
4.2 MAC
头
(M
HDR
字段
)
Bit#
MHDR
bits
7..5
MType
4..2
RFU
1..0
Major
MAC
头中指定了消息类
型
(MType)
和帧编码所遵循的
L
oRaWAN
规范的主版本号
(Major)
< br>。
4.2.1
消息类型
p>
(MType
位字段
)
< br>LoRaWAN
定义了六个不同的
MAC
消息类型:
join request, join accept,
unconfirmed
data up/down,
以及
confirmed data
up/down
。
MType
描述
Join Request
Join Accept
Unconfirmed Data
Up
Unconfirmed Data Down
Confirmed Data Up
Confirmed
Data Down
RFU
Proprietary
000
001
010
011
100
101
110
111
表
消息类型
?
4.2.1.1 Join-
request and join-accept
消息
join-
request
和
join-
accept
都是用在空中激活流程中,具体见章节
6.2
?
4.2.1.2 Data
messages
Data messages
用来传输<
/p>
MAC
命令和应用数据,这两种命令也可以放在单个消息中
发送。
Confirmed-data
message
接收者需要应答。
Unconfirmed-data message
接收者则不需要应答。
Proprietary messages
用来处理非标准
的消息格式,不能和标准消息互通,只能用来
和具有相同拓展格式的消息进行通信。
p>
不同消息类型用不同的方法保证消息一致性,
下面会介绍每种消息类型的具体情况。
4.2.2
p>
数据消息的主版本
(Major
位字段
p>
)
Major
位字段
00
01..11
表
列表
描述
LoRaW
AN R1
RFU
p>
注意:
Major
定义了激活过程中
(join procedure)
使用的消息格式
< br>(见章节
6.2
)
和
MAC
Payload
的前
4
字节
(见第
4
< br>章)
。终端要根据不同的主版本号实现不同最小版本的
消
息格式。终端使用的最小版本应当提前通知网络服务器。
4.3
MAC
载荷
(MACPayload)
MAC
载荷,也就是所谓的
“
数据帧
”
,包含:帧头(
FHDR
)、端口(
FPort
)以及帧
< br>载荷
(FRMPayload
),其中端口和帧载荷是可
选的。
4.3.1
帧头
(FHDR)
FHDR
是由终端短地址
(DevAddr)
、
1
字节帧控制字节
(FCtrl)
、
2
字节帧计数器
(FCn
t)
和用来传输
MAC
命令的帧选项<
/p>
(FOpts
,最多
15
个字节
)
组成。
Size(bytes)
FHDR
4
DevAddr
1
FCtrl
2
FCnt
0..15
FOpts
FCtrl
在上下行消息
中有所不同,下行消息如下:
Bit#
FCtrl
bits
7
ADR
6
ADRACKReq
5
ACK
4
FPending
[3..0]
FOptsLen
上行消息如下:
Bit#
FCtrl
bits
7
ADR
6
ADRACKReq
5
ACK
4
RFU
[3..0]
FOptsLen
?
4.3.1.1
帧头中自适应数据速率的控制
(ADR, ADRACKReq
in FCtrl)
LoRa
网络允许终端采用任何可能的数
据速率。
LoRaWAN
协议利用该特性来优化固
定终端的数据速率。
这就是自适应数据速率
(Ada
ptive Data Rate (ADR))
。
当这个使能
时,网络会优化使得尽可能使用最快的数据速率。
移动的终端由于射频环境的快速变化,数据速率管理就不再适用了,应当使用固定
< br>的数据速率。
如果
ADR
p>
的位字段有置位,
网络就会通过相应的
MA
C
命令来控制终端设备的数据
速率。如果
ADR
位没设置,网络则无视终端的接收信号强度,不再控制终端设备
的数据速率。
ADR
位可以根据需要通过终端及网络来
设置或取消。
不管怎样,
ADR
机制都
应该尽可能使能,帮助终端延长电池寿命和扩大网络容量。
注
意:即使是移动的终端,可能在大部分时间也是处于非移动状态。因此根据它的
移动状态
,终端也可以请求网络使用
ADR
来帮助优化数据速率。
如果终端被网络优化过的数据速率高于自己默认的数据速率,它需要
定期检查下网
络仍能收到上行的数据。每次上行帧计数都会累加
(
是针对于每个新的上行包,重传
包就不再增加计数
)
,
终端增加
ADR_ACK_CNT
计数。
如果
直到
ADR_ACK_LIMIT
次上行
(ADR_ACK_CNT >= ADR_ACK_LIMIT)
都没有收到下行回
复,它就得置高
ADR
应答请求位
(<
/p>
ADRACKReq
)
。网络必须在规定
时间内回复一个下行帧,这个时
间是通过
ADR_ACK_DE
LAY
来设置,上行之后收到任何下行帧就要把
ADR_ACK
_CNT
的计数重置。当终端在接收时隙中的任何回复下行帧的
ACK
位字
段不需要设置,表示网关仍在接收这个设备的上行帧
。如果在下一个
ADR_ACK_DELAY
上行时间内都没收
到回复
(
例如,在总时间
ADR_AC
K_LIMIT+ADR_ACK_DELAY
之后
)
,终端必须切换到下一个更低速率,
使得能够获得更远传输距离来重连网
络。
终端如果在每次
ADR_ACK_LIMIT
到了之
后依旧连接不上,就需要每次逐步降低数据速率。如果终端用它的默认
数据速率,
那就不需要置位
ADRACKReq
,因为无法帮助提高链路距离。
注意:不要
ADRACKReq
立刻回复,这样给网络预留一些余量,让它做出最好
的下
行调度处理。
注意:上行传输时,如果
ADR_ACK_CNT >= ADR_ACK_LIMIT
并且当前数据速
率比设备的最小数据速率高,就要设置
p>
ADRACKReq
,其它情况下不需要。
?
4.3.1.2
消息应答位及应答流程
(ACK in FCtrl)
收到
confirmed
类型的消息时,
p>
接收端要回复一条应答消息
(
应答位
ACK
要进行置位
)
。
如果发送者是终端,
网络就利用终端发送操作后打开的两个接
收窗口之一进行回复。
如果发送者是网关,终端就自行决定是否发送应答。
应答消息只会在收到消息后回复发送,并且不重发。
注意:为了让终端尽可能简单,尽可能减少状态,在收到
confirm
ation
类型需要确
认的数据帧,需要立即发送一个严格的应
答数据帧。或者,终端会延迟发送应答,
在它下一个数据帧中再携带。
< br>
?
4.3.1.3
重传流程
当需要应答却没收到应答时
就会进行重发,重发的个数由终端自己定,可能每个终
端都不一样,这个参数也可以由网
络服务器来设置调整。
注意:一些应答机制的示例时序图在第
18
章中有提供。
< br>注意:如果终端设备重发次数到达了最大值,它可以降低数据速率来重连。至于后
面是否再重发还是说丢弃不管,都取决于终端自己。
注意:如
果网络服务器重发次数到达了最大值,它就认为该终端掉线了,直到它再
收到终端的消息
。一旦和终端设备的连接出现问题时,要不要重发都取决于网络服
务器自己。
注意:在重传期间的数据速率回退的建议策略在章节
18.4
中有描述。
?
4.3.1.4
帧挂起位
(FPending in FCtrl
只在下行有效
)
帧挂起位
(FPending)
只在下行交互中使用,
表示
网关还有挂起数据等待下发,
需要终
端尽快发送上行消息来再打
开一个接收窗口。
FPending
的详细用法在章节
18.3
。
?
4.3.1.5
帧计数器
(FCnt)
每个终端有两
个计数器跟踪数据帧的个数,一个是上行链路计数器(
FCntUp
),由
终端在每次上行数据给网络服务器时累加;
另一个是
下行链路计数器
(
FCntDown
)
,
由服务器在每次下行数据给终端时累计。网络服务器为每个终
端跟踪上行帧计数及
产生下行帧计数。终端入网成功后,终端和服务端的上下行帧计数同
时置
0
。每次
发送消息后,
发送端与之对应的
FCntUp
或
FCntDown
就会加
1
。
接收方会同步保
存接收数据的帧计数,对比收到的计数值和当前保存的值,如果两者相差小于
MAX_FCNT_GAP
(要考虑计数器滚
动),接收方就按接收的帧计数更新对应值。
如果两者相差大于
MAX_FCNY_GAP
就说明中间丢失了很多数据,这条
以及后面
的数据就被丢掉。
LoRa
WAN
的帧计数器可以用
16
位和
p>
32
位两种,节点上具体执行哪种计数,需要
在带外通知网络侧,告知计数器的位数。
如果采用
16
位帧计数,
FCnt
字段的值可以使用帧计数器的值,此时有需要的话通
过在前面填充
< br>0
(值为
0
)字节来补足;如果
采用
32
位帧计数,
FCnt
就对应计数器
32
位的
16
个低有效位
(
上行数据使用上行
FCnt
,下行数据使用
下行
FCnt)
。
p>
终端在相同应用和网络密钥下,不能重复用相同的
FCntUp
p>
数值,除非是重传。
?
4.3.1.6
帧可选项
(FOptsLen in FCtrl,
FOpts)
FCtrl
字节中的
FOptsLen
位字段描述了整个帧可选项
(FOpts)
的字段长度。
FOpts
字段存放
MAC
命令,最长
15
字节,详细的
MAC
命令见章节
4.4
。
如
果
FOptsLen
为
0
,则
FOpts
为空。在
F
OptsLen
非
0
时,则反之。如果
MAC
命令
在
FOpts
字段中体现,
port0
不
能用
(FPort
要么不体现,要么非
0)
。
MAC
命令不能同时出现在
FRMPayload
和
FOpts
中,如果出现了,设备丢掉该组数
据。
p>
4.3.2
端口字段
(FPort)
如果帧载荷
字段不为空,端口字段必须体现出来。端口字段有体现时,若
FPort
的
值为
0
表示
FRMPayload
只包含了
MAC
命令;
具体见章节
4.4
中的
MAC
命令。
FPort
的数值从
1
到
223(0x01..0xDF)
都是由应用层使用。
FPort
的值从
224
到
255(0xE0..0xFF)
是保留用
做未来的标准应用拓展。
Size(bytes)
MACPayload
7..23
FHDR
0..1
FPort
0..N
FRMPayload
N
是应用程序载荷的字节个数。
N
的有效
范围具体在第
7
章有定义。
N
应该小于等于:
N <= M - 1 -
(FHDR
长度
)
M
是
MAC
载荷的最大长度。
4.3.3
MAC
帧载荷加密
(FRMPayload)
如果数据帧携带了载荷,
FRMPayload
必须要
在
MIC
计算前进行加密。
加密机制是采用
IEEE802.15.4/2006
的
AES128
算法
。
默认的,加密和加密由
LoRaWAN
p>
层来给所有的
FPort
来执行。如果加密
/
解密由应
用层来做更方便的话,也可
以在
LoRaWAN
层之上给特定
FP
orts
来执行,除了端口
0
。具体哪
个节点的哪个
FPort
在
LoRaW
AN
层之外要做加解密,必须要和服务器通
过
< br>out-of-band
信道来交互
(
< br>见第
19
章
)
< br>。
?
4.3.3.1
LoRaW
AN
的加密
密钥
K
根据不同的
FPor
t
来使用:
FPort
0
1..255
表
3:
FPort
列表
具体加密是这样:
pld =
FRMPayload
对于每个数据帧,算法定义了一个块序列
Ai
,
i
从
1
到
k
,
k
= ceil(len(pld) / 16)
:
Size(bytes)
Ai
1
4
1
4
4
FCntUp or FCntDown
1
1
K
NwkSKey
AppSKey
0x01
4 x 0x00
Dir
DevAddr
0x00
i
p>
方向字段
(
Dir
)
在上行帧时为
0
,在下行帧时为
p>
1.
块
Ai
通
过加密,得到一个由块
Si
组成的序列
S
。
Si =
aes128_encrypt(K, Ai) for i = 1..k
S =
S1 | S2 | .. | Sk
通过异或计算对
pay
load
进行加解密:
?
4.3.3.2
LoRaW
AN
层之上的加密
p>
如果
LoRaW
AN
之上的层级在已选的端口上
(
但不能是端口
< br>0
,这是给
MAC
命令保留的<
/p>
)
提供了预加密的
FRMPayload
给
LoRaW
AN
,
LoRaW
AN
则不再
对
FRMPayload
进行修改,
直接将
FRMPayload
从
< br>MACPayload
传到应用层,
以及从应用层传到<
/p>
MACPayload
。
4.4
消息校验码
(MIC)
消息检验码要计算消息中所有字段。
msg = MHDR | FHDR | FPort | FRMPayload
MIC
是按照
[RFC4493]
p>
来计算:
cmac =
aes128_cmac(NwkSKey, B0 | msg)
MIC =
cmac[0..3]
块
B0
的定义
如下:
Size(bytes)
1
B0
0x49
4
1
4
4
1
1
len(msg)
4
x
Dir
0x00
DevAddr
FCntUp
or
0x00
FCntDown
方向字
段
(
Dir
)
在上行帧时为
0
,在下行帧时为
1.
LoRaWAN
第
4
< br>章,主要讲述了
MAC
帧格式,对所有涉及的字段都做了
解释。
千言万语汇成一句话,哦不,汇成一个表。
数
Dev
Addr
FC
trl
F
Cn
FO
pts
据
帧
头
Prea
mble
PH
DR
PHDR
_CRC
MH
DR
FHDR
t
数
据
帧
M
AC
层
PH
Y
层
FP
ort
FRMPa
yload
M
C
IC
R
C
Prea
mble
PH
DR
PHDR
_CRC
MH
DR
MACPayload
M
C
IC
R
C
Prea
mble
PH
DR
PHDR
_CRC
PHYPayload
C
R
C
<
/p>
好了,帧格式是大家随手都能看到的东西,本尊作为
IoT
小能手,如果不能提出一
些稍有深度的信息增量,就对不起这个称号了
。所以,有些协议设计层面的心得要
分享下:
1.
特别酷的
ADR(
速率自适应
)
机制
这个章节中最亮眼的莫过于速率自适应机制,简直是为
LoRa
网络量身定做
的:一旦使能了
FCtrl
中的
ADR
位,
距离近信号好的节点用高速率,距离远
信号弱的节点用低速率,不小心被调高了速率,则
自动降下来。这样,尽可
能地提高了传输速率,也有效提高了网络容量。我已经见过不少
厂家,拿这
个协议的公知特点当产品卖点了。
2.
可同时携带数据和命令的
MAC
帧
一般来
说,应用除了数据,出于管理需要,肯定还会涉及命令。比如基站要
查询节点状态,或者
节点要请求变更信道等。所以
LoRaWAN
协议设计上利
p>
用
FOpts
把数据和命令揉在一个
MAC
帧里,这样可以提高交互效率,有效
地
降低功耗。
这在寸土寸金,
哦不,
寸库
仑
(
电量单位
)
寸金的物联网应用中,
是一个很有必要的设计。
3
源码解析
这章的处理基本都在
srcmacLoRaMac.c
中,下面按照
MAC
帧格式的字段逐个解
析下。
< br>
3.1
MAC
层
MHDR
< br>在
LoRaWAN
的数据
API
中处理了
MHDR
,
< br>这个字段内容比较少,
就按需选择了消
息类型是
confirm
还是
unconfirm
。
另外在管理
A
PI
中的
Join-
Req
的消息类型。
具体可见
LoRaMacMcpsRequest()
和
LoRaMacMlmeRequest()
这两个函数。
3.2
MACPayload
MACPayload
的组帧都在
PrepareFrame()
这个函数中处理,将
macHdr
和
macPayload
的
fCtrl
、
FPo
rt
、
FRMPayload
都传递进
去,完成整个
MAC
层的数据组
帧。<
/p>
LoRaMacBuffer
就存放了
MACPayload
的数据,这个变量的组帧和协议字段定义
是
一一对应。
MACPayload
的
组帧处理,在大流程上是对
join
和数据两种类型的帧分
p>
别处理,用两个
case
分开。
为了方便阅览,我把函数代码框架提炼了出来。
LoRaMacStatus_t PrepareFrame(
LoRaMacHeader_t *macHdr,
LoR
aMacFrameCtrl_t *fCtrl, uint8_t
fPort, void *fBuffer, uint
16_t
fBufferSize )
{
switch(
macHdr-> )
{
case
FRAME_TYPE_JOIN_REQ:
...//
省略
break;
case FRAME_TYPE_DATA_CONFIRMED_UP:
NodeAckRequested = true;
//Intentional falltrough
case FRAME_TYPE_DATA_UNCONFIRMED_UP:
...
fCtrl->Req =
AdrNextDr( fCtrl->, true,
&lsDatarate
);
...
if(
SrvAckRequested == true )
{
SrvAckRequested =
false;
fCtrl-> = 1;
}
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr >> 8 ) & 0
xFF;
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr >> 16 ) &
0xFF;
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr >> 24 ) &
0xFF;
LoRaMacBuffer[pktHeaderLen++] =
fCtrl->Value;
LoRaMacBuffer[pktHeaderLen++] =
UpLinkCounter & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = (
UpLinkCounter >> 8 ) & 0x
FF;
// Copy the MAC
commands which must be re-send in
to the
MAC command buffer
memcpy1(&MacCommands
Buffer[MacCommandsBufferIndex],
MacCo
mmandsBufferToRepeat,
MacCommandsBufferToRepeatIndex );
MacCommandsBufferIndex +=
MacCommandsBufferToRep
eatIndex;
if( ( payload != NULL ) &&
( payloadSize > 0 ) )
{
if( ( MacCommandsBufferIndex <=
LORA_MAC_COMMAND_MAX_LENGT
H ) && (
MacCommandsInNextTx == true ) )
{
fCtrl->en += MacCommandsBufferIndex;
//
Update FCtrl field with new value of
Op
tionsLength
LoRaMacBuffer[0x05] = fCtrl->Value;
for( i = 0; i < MacCommandsBufferIndex;
i++ )
{
LoRaMacBuffer[pktHeaderLen++] =
MacCommandsBuffer[i];
}
}
}
else
{
if( ( MacCommandsBufferIndex > 0 ) && (
MacCommandsInNextTx
) )
{
payloadSize = MacCommandsBufferIndex;
payload = MacCommandsBuffer;
framePort = 0;
}
}
MacCommandsInNextTx = false;
// Store MAC commands which must be re-send in
ca
se the device does not receive a
downlink anymore
MacCommandsBufferToRepeatIndex =
ParseMacCommand
sToRepeat(
MacCommandsBuffer, MacCommandsBufferIndex,
MacC
ommandsBufferToRepeat );
if( MacCommandsBufferToRepeatIndex > 0
)
{
MacCommandsInNextTx = true;
}
MacCommandsBufferIndex =
0;
if( ( payload != NULL )
&& ( payloadSize > 0 ) )
{
LoRaMacBuffer[pktHeaderLen++] =
framePort;
if( framePort ==
0 )
{
LoRaMacPayloadEncrypt( (uint8_t* )
payload, payloadSize, L
oRaMacNwkSKey,
LoRaMacDevAddr, UP_LINK, UpLinkCounter,
LoR
aMacPayload );
}
else
{
LoRaMacPayloadEncrypt( (uint8_t* )
payload, payloadSize, L
oRaMacAppSKey,
LoRaMacDevAddr, UP_LINK, UpLinkCounter,
LoR
aMacPayload );
}
memcpy1( LoRaMacBuffer +
pktHeaderLen, LoRaMacPayload,
pay
loadSize );
}
LoRaMacBufferPktLen =
pktHeaderLen + payloadSiz
e;
LoRaMacComputeMic( LoRaMacBuffer,
LoRaMacBufferPktLen, LoR
aMacNwkSKey,
LoRaMacDevAddr, UP_LINK, UpLinkCounter,
&mic
);
LoRaMacBuffer[LoRaMacBufferPktLen + 0]
= mic & 0xFF;
LoRaMacBuffer[LoRaMacBufferPktLen + 1]
= ( mic >> 8 ) & 0xF
F;
LoRaMacBuffer[LoRaMacBufferPktLen + 2]
= ( mic >> 16 ) & 0x
FF;
LoRaMacBuffer[LoRaMacBufferPktLen + 3]
= ( mic >> 24 ) & 0x
FF;
LoRaMacBufferPktLen +=
LORAMAC_MFR_LEN;
break;
case FRAME_TYPE_PROPRIETARY:
...//
省略
break;
default:
return
LORAMAC_STATUS_SERVICE_UNKNOWN;
}
return LORAMAC_STATUS_OK;
}
Join-request
的组帧
处理对应协议第
6
章
6.2.4 Join-request
message
。
数据帧的组帧处
理则稍微复杂些,尤其是
FHDR
,下面逐个字段讲解下
FHDR
。
3.2.1
MACPayload
中的
FHDR
?
中的
DevAddr
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr >> 8 ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr >> 16 ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = (
LoRaMacDevAddr >> 24 ) & 0xFF;
?
中的
FCtrl
首先
ADR
位段是在传入
PrepareFrame()
之前,就做了处理。
=
AdrCtrlOn;
接着
AdrAckReq
位段,在长期失联情况下会发送
AdrAckReq
确认链路。
fCtrl->Req = AdrNextDr( fCtrl->, true,
&lsDatarate );
最后
F0ptsLen
位段,会在下面计算完
FOpts
之后更新。
?
中的
FCnt
LoRaMacBuffer[pktHeaderLen++] =
UpLinkCounter & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = (
UpLinkCounter >> 8 ) & 0xFF;
这个
< br>UpLinkCounter
会在物理层发送完成后会按照协议进行累加。可以看
到这是个
32
位计数器,按照协议规定,
“
如果采用
32
位帧计数,
FCnt
就对应计数器
32
位的
16
个低有效位
”
。
这是上行的,另外下行的也类似。
?
中的
FOpts
把
MAC
命令放入
F0pts
p>
中,
并且更新
F0ptsLen
。
MAC
命令,
要么使用
非零的
FPort
来和数据一起传输,要么使用
FPort0
来单独传输。
// Copy the MAC commands which must be
re-send into the MAC
command buffer
memcpy1(&MacCommandsBuffer[MacCommandsBuff
erIndex], MacCo
mmandsBufferToRepeat,
MacCommandsBufferToRepeatIndex );
MacCommandsBufferIndex +=
MacCommandsBufferToRepeatIndex;
if( ( payload != NULL ) && (
payloadSize > 0 ) )
{
if( (
MacCommandsBufferIndex <=
LORA_MAC_COMMAND_MAX_LENGT
H ) && (
MacCommandsInNextTx == true ) )
{
fCtrl->en += MacCommandsBufferIndex;
// Update FCtrl
field with new value of OptionsLengt
h
LoRaMacBuffer[0x05] = fCtrl->Value;
for( i = 0; i < MacCommandsBufferIndex;
i++ )
{
LoRaMacBuffer[pktHeaderLen++] =
MacCommandsBuffer[i];
}
}
}
else
{
if( (
MacCommandsBufferIndex > 0 ) && (
MacCommandsInNextTx
) )
{
payloadSize = MacCommandsBufferIndex;
payload = MacCommandsBuffer;
framePort = 0;
}
}
3.2.2
MACPayload
中的
FPort
这个是在应用层一直传递进去的,协议栈默认是用了端口
2
。
这个是后期大家在应
用时要调整的,类似于
IP
端口,不同的端口对应不同的服务。
3.3
MIC
解析
在函数
PrepareFrame(
)
的最后是调用
LoRaMacComputeMic() <
/p>
计算出整个
MAC
层的
< br>校验码。应用层这边基本不用改这边就暂时不细究了。
第
5
章
MAC
命令
对网络管理者而言,
有一套专门的
MAC
命令用来在服务器和终端
MAC
层之间交互。
这套
MAC
命令对应用程序
(
不管是服务器端还是终端设备的应用程序
)
是不可见的。
单个数据帧中可以携带
MAC
命令,要么在
FOpts
字
段中捎带,要么在独立帧中将
FPort
设成
< br>0
后放在
FRMPayload
里。如果采用
FOpts
捎带的方式,
MAC
命令是不
加密并且不长度超过
1
5
字节。如果采用独立帧放在
FRMPayload
的方式,那就必须
采用加密方式,并且不超过
FR
MPayload
的最大长度。
注意
:如果
MAC
命令不想被窃听,那就必须以独立帧形式放在
p>
FRMPayload
中。
每个
MAC
命令是由
1
字节
CID
跟着一段可能为空的字节序列组成的。
CID
Command
由谁发送
终
端
0x02
0x02
LinkCheckReq
LinkCheckAns
x
网
关
x
终端利用这个命令来判断网络连接质量
描述
LinkCheckReq
p>
的回复。包含接收信号强度,告知终端
收质量
0x03
0x03
LinkADRReq
LinkADRAns
x
x
向终端请求改变数据速率,发射功率,重传率以及信道
Link
ADRReq
的回复。
0x04
0x04
0x05
0x05
0x06
0x06
0x07
0x07
0x08
0x08
DutyCycleReq
DutyCycleAns
RXParamSetupReq
RXParamSetupAns
DevStatusReq
DevStatusAns
NewChannelReq
NewChannelAns
x
x
x
x
x
x
x
x
x
x
向终端设置发送的最大占空比。
DutyCycleReq
的回复。
向终端设置接收时隙参数。
RXPa
ramSetupReq
的回复。
向终端查询其状态。
返回终端设备的状态,即电池余量和链路解调预算。
创建或修改
1
个射频信道定义。
NewChannelReq
的回复。
设置接收时隙的时间。
RXTimi
ngSetupReq
的回复。
给私有网络命令拓展做预留。
RXTimingSetupReq
RXTimingSetupAns
x
x
0x80~0xFF
私有
表
4<
/p>
:
MAC
命令表
注意:
MAC
命令的长度虽然没有明确
给出,但是
MAC
执行层必须要知道。因此未
< br>知的
MAC
命令无法被忽略,
且
前面未知的
MAC
命令会终止
MAC<
/p>
命令的处理队列。
所以建议按照
LoRa
WAN
协议介绍的
MAC
命令来处理<
/p>
MAC
命令。这样所有基于
LoRaWA
N
协议的
MAC
命令都可以被处理,即
使是更高版本的命令。
2
梳理解析
从
LoRaWAN
第
4
章的帧格式可以得
到如下信息:
MAC
命令,要么使用
F
Port0
来
单独传输,要么使用非零的
FPort
来和数据一起传输。
L
oRaWAN
第
5
章,
LoRaWAN
出于网络管理需要,
提出了
9
条
MAC
命令,
这个章
节是对
9
条命
令进行具体的描述。
说个题外话,
C
LAA(
中国
LoRa
应用联盟
)
在
9
条命令以外还
扩充了一些
MAC
命令。
现阶段协议还
不能公开,
所以我就不多说了。
中兴目前作为
< br>LoRa
联盟董事会成员,
也许以后会把这些拓展
MAC
命令引入到
LoRaWAN
协议也说不准,大家暂且当个
课外知识了解下就好。
3
代码位置
MAC
命令枚举
/*!
* LoRaMAC mote MAC
commands
*
* LoRaWAN
Specification V1.0.1, chapter 5, table 4
*/
typedef enum
eLoRaMacMoteCmd
{
/*!
* LinkCheckReq
*/
MOTE_MAC_LINK_CHECK_REQ =
0x02,
/*!
*
LinkADRAns
*/
MOTE_MAC_LINK_ADR_ANS = 0x03,
/*!
* DutyCycleAns
*/
MOTE_MAC_DUTY_CYCLE_ANS = 0x04,
/*!
*
RXParamSetupAns
*/
MOTE_MAC_RX_PARAM_SETUP_ANS = 0x05,
/*!
* DevStatusAns
*/
MOTE_MAC_DEV_STATUS_ANS = 0x06,
/*!
* NewChannelAns
*/
MOTE_MAC_NEW_CHANNEL_ANS = 0x07,
/*!
*
RXTimingSetupAns
*/
MOTE_MAC_RX_TIMING_SETUP_ANS = 0x08,
}LoRaMacMoteCmd_t;
/*!
* LoRaMAC server MAC commands
*
* LoRaWAN Specification V1.0.1 chapter
5, table 4
*/
typedef enum
eLoRaMacSrvCmd
{
/*!
* LinkCheckAns
*/
SRV_MAC_LINK_CHECK_ANS =
0x02,
/*!
*
LinkADRReq
*/
SRV_MAC_LINK_ADR_REQ = 0x03,
/*!
* DutyCycleReq
*/
SRV_MAC_DUTY_CYCLE_REQ = 0x04,
/*!
*
RXParamSetupReq
*/
SRV_MAC_RX_PARAM_SETUP_REQ = 0x05,
/*!
* DevStatusReq
*/
SRV_MAC_DEV_STATUS_REQ = 0x06,
/*!
* NewChannelReq
*/
SRV_MAC_NEW_CHANNEL_REQ = 0x07,
/*!
*
RXTimingSetupReq
*/
SRV_MAC_RX_TIMING_SETUP_REQ = 0x08,
}LoRaMacSrvCmd_t;
MAC
命令的接收处理
OnRadioRxDone()
携带着
MAC
p>
帧进来,经过层层筛选,最终到达
ProcessMacComma
nds()
来处理
MAC
命令。
这里代码中涉及的两种处理方式,可以跟协议对应起来:
port = 0
时,
MAC
< br>命令放
在
FRMPayload
中,需要先解密再处理;
port
非零时,
MAC
命令放在
fopts
中。<
/p>
if( port == 0 )
{
if( en == 0 )
{
LoRaMacPayloadDecrypt( payload +
appPayloadStartIndex,
frameLen,
nwkSKey,
address,
DOWN_LINK,
downLinkCounter,
LoRaMacRxPayload );
// Decode frame payload MAC
commands
ProcessMacCommands(
LoRaMacRxPayload, 0, frameLen, snr );
}
} else {
if( en > 0 )
{
// Decode
Options field MAC commands. Omit the
fPor
t.
ProcessMacCommands(
payload, 8, appPayloadStartIndex - 1,
snr );
}
}
MAC
命令的发送及回复
MAC
命令的发送及回复处理都在这个函数中,
A
ddMacCommand()
。
协
议栈对
MAC
命令发送的处理还是比较简单的,
都是放在
Fopts
中来传输,
都在这
个
15
字节的
MacCommandsBuffer
中。
LinkADR
是
LoRaWAN
网络管理中相当重要的一个
MAC
命令,其解析占用
了
183
行。索性专门写篇源码解析,记录下。
阅读此文前,最好再把第五章的这个命令好好翻一翻,代码和协议才能对应上
。
我正在陆续对协议的各个章节进行翻译,具体其他章节的译
文,以及译文之外的代
码解析,可点此查看帖子
LoRa
学习笔记
_
汇总
。<
/p>
本文作者
twowinter
,转载请注明作者:
/iotisan/
LinkADRReq
的源码解析
按照代码思路走一遍。
1.
解析
DataRate_TXPower
字段
datarate =
payload[macIndex++];
txPower = datarate
& 0x0F;
datarate = ( datarate >> 4 ) &
0x0F;
if( ( AdrCtrlOn ==
false ) &&
( ( lsDatarate != datarate )
|| ( LoRaM
lsTxPower != txPower ) ) )
{ // ADR disabled don't handle ADR
requests if server tries
to change
datarate or txpower
// Answer the
server with fail status
// Power
ACK = 0
// Data rate ACK = 0
// Channel mask = 0
AddMacCommand( MOTE_MAC_LINK_ADR_ANS,
0, 0 );
macIndex += 3; // Skip over
the remaining bytes of the req
uest
break;
}
如果终端
ADR
没开,那么就立即丢弃本命令处理。这里的
macIndex += 3
是对应
LinkADRReq<
/p>
的剩余命令长度
3
而言的。
2.
解析
ChMask
字段
chMask = ( uint16_t
)payload[macIndex++];
chMask |= (
uint16_t )payload[macIndex++] << 8;
3.
解析
Redundancy
字段
nbRep = payload[macIndex++];
chMaskCntl = ( nbRep >> 4 ) & 0x07;
nbRep&= 0x0F;
if( nbRep == 0
)
{
nbRep = 1;
}
把字段中的
chMaskCntl
和
nbRep
都给解析了出来。
4.
按地区规定处理
chMaskCntl
,及判断
ChMask
有效性
#elif defined( USE_BAND_470 )
if( chMaskCntl == 6 )
{
// Enable all 125 kHz channels
for( uint8_t i = 0, k = 0; i <
LORA_MAX_NB_CHANNELS; i += 1
6, k++ )
{
for( uint8_t j =
0; j < 16; j++ )
{
if( Channels[i + j].Frequency != 0 )
{
channelsMask[k] |= 1 << j;
}
}
}
}
else if( chMaskCntl ==
7 )
{
status&= 0xFE; //
Channel mask KO
}
else
{
for( uint8_t i = 0; i
< 16; i++ )
{
if( ( ( chMask & ( 1 << i ) ) != 0 ) &&
( Channels[chMaskCntl * 16 +
i].Frequency == 0 ) )
{//
Trying to enable an undefined channel
status&= 0xFE; // Channel mask KO
}
}
channelsMask[chMaskCntl] = chMask;
}
如果
chMaskCntl
为
6
,则所有信道都使能。如果
chMaskCntl
为
7
,则由于未定义
返回失败。
其他有效
chMaskCntl <
/p>
情况下,先检查是否有未定义的频点,如果没问题则更新对
应的<
/p>
channelsMask
。
5.
判断速率有效性
if( ValidateDatarate( datarate,
channelsMask ) == false )
{
status&= 0xFD; // Datarate KO
}
6.
判断发射功率有效性
if( ValueInRange( txPower,
LORAMAC_MAX_TX_POWER,
LORAMAC_M
IN_TX_POWER ) == false )
-
-
-
-
-
-
-
-
-
上一篇:DERRICK德瑞克
下一篇:圣诞节英语作文圣诞节活动英语作文