MAC 命令下行,目前和数据下行一样,主要也是在 generateDownlink() 进行处理。
小能手这段时间在学习 The Things Network LoRaWAN Stack V3,从使用和代码等角度对该 Stack 进行了分析,详细可点此查看。
dev.MACState.PendingRequests = dev.MACState.PendingRequests[:0]
maxDownLen, maxUpLen, ok, err := enqueueLinkADRReq(ctx, dev, maxDownLen, maxUpLen, ns.FrequencyPlans)
if err != nil {
return nil, err
}
fPending := !ok
for _, f := range []func(context.Context, *ttnpb.EndDevice, uint16, uint16) (uint16, uint16, bool){
// LoRaWAN 1.0+
enqueueNewChannelReq,
enqueueDutyCycleReq,
enqueueRxParamSetupReq,
func(ctx context.Context, dev *ttnpb.EndDevice, maxDownLen uint16, maxUpLen uint16) (uint16, uint16, bool) {
return enqueueDevStatusReq(ctx, dev, maxDownLen, maxUpLen, ns.defaultMACSettings)
},
enqueueRxTimingSetupReq,
enqueuePingSlotChannelReq,
enqueueBeaconFreqReq,
// LoRaWAN 1.0.2+
enqueueTxParamSetupReq,
enqueueDLChannelReq,
// LoRaWAN 1.1+
enqueueADRParamSetupReq,
enqueueForceRejoinReq,
enqueueRejoinParamSetupReq,
} {
var ok bool
maxDownLen, maxUpLen, ok = f(ctx, dev, maxDownLen, maxUpLen)
fPending = fPending || !ok
}
cmds = append(cmds, dev.MACState.PendingRequests...)
在各类 MAC 命令的子模块中,都会调用 enqueueMACCommand() 来进行 MAC 命令的处理。这里得到的 cmds 传入 enqueueMACCommand() 函数中,再把 MAC 命令传递给 dev.MACState.PendingRequests。
func enqueueMACCommand(cid ttnpb.MACCommandIdentifier, maxDownLen, maxUpLen uint16, f func(nDown, nUp uint16) ([]*ttnpb.MACCommand, uint16, bool), cmds ...*ttnpb.MACCommand) ([]*ttnpb.MACCommand, uint16, uint16, bool) {
desc := lorawan.DefaultMACCommands[cid]
maxDown := maxDownLen / (1 + desc.DownlinkLength)
maxUp := maxUpLen / (1 + desc.UplinkLength)
enq, nUp, ok := f(maxDown, maxUp)
if len(enq) > int(maxDown) || nUp > maxUp {
panic("invalid amount of MAC commands enqueued")
}
return append(cmds, enq...), maxDownLen - uint16(len(enq))*desc.DownlinkLength, maxUpLen - nUp*desc.UplinkLength, ok
}
这里有个格式转化,初看的时候被绕蒙圈了,以 LinkADRReq 为例,做了个梳理。
函数 | 类型 | 包含内容 |
---|---|---|
cmds | *ttnpb.MACCommand | CID + Payload |
pld.MACCommand() | *MACCommand | CID + Payload |
cmd.GetLinkADRReq() | *MACCommand_LinkADRReq | Payload |
pld | *MACCommand_LinkADRReq | Payload |
MACCommand() 是从 payload 获得完整 MAC 命令,GetPayload() 或者具体的 GetLinkADRReq() 是从 MAC 命令获得具体的 Payload。
// MACCommand returns the LinkADRReq MAC command as a *MACCommand.
func (pld *MACCommand_LinkADRReq) MACCommand() *MACCommand {
return &MACCommand{
CID: CID_LINK_ADR,
Payload: &MACCommand_LinkADRReq_{
LinkADRReq: pld,
},
}
}
func (m *MACCommand) GetLinkADRReq() *MACCommand_LinkADRReq {
if x, ok := m.GetPayload().(*MACCommand_LinkADRReq_); ok {
return x.LinkADRReq
}
return nil
}
type MACCommand struct {
CID MACCommandIdentifier `protobuf:"varint,1,opt,name=cid,proto3,enum=ttn.lorawan.v3.MACCommandIdentifier" json:"cid,omitempty"`
Payload isMACCommand_Payload `protobuf_oneof:"payload"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_sizecache int32 `json:"-"`
}
前头准备好的各类 MAC 命令,即 dev.MACState.PendingRequests,在这一步会被打散传入 cmds,进行组帧下发。
cmds = append(cmds, dev.MACState.PendingRequests...)
...
cmdBuf := make([]byte, 0, maxDownLen)
for _, cmd := range cmds {
logger := logger.WithField("cid", cmd.CID)
logger.Debug("Add MAC command to buffer")
var err error
cmdBuf, err = spec.AppendDownlink(phy, cmdBuf, *cmd)
if err != nil {
return nil, errEncodeMAC.WithCause(err)
}
if mType == ttnpb.MType_UNCONFIRMED_DOWN && spec[cmd.CID].ExpectAnswer && dev.MACState.DeviceClass == ttnpb.CLASS_C {
logger.Debug("Use confirmed downlink to get immediate answer")
mType = ttnpb.MType_CONFIRMED_DOWN
}
}
cmds 通过 spec.AppendDownlink() 传入,接下来的几步都在 mac.go 中进行处理。
mType := ttnpb.MType_UNCONFIRMED_DOWN
cmdBuf := make([]byte, 0, maxDownLen)
for _, cmd := range cmds {
logger := logger.WithField("cid", cmd.CID)
logger.Debug("Add MAC command to buffer")
var err error
cmdBuf, err = spec.AppendDownlink(phy, cmdBuf, *cmd)
if err != nil {
return nil, errEncodeMAC.WithCause(err)
}
if mType == ttnpb.MType_UNCONFIRMED_DOWN && spec[cmd.CID].ExpectAnswer && dev.MACState.DeviceClass == ttnpb.CLASS_C {
logger.Debug("Use confirmed downlink to get immediate answer")
mType = ttnpb.MType_CONFIRMED_DOWN
}
}
一层层传递下去,AppendDownlink -> desc.AppendDownlink -> 各个MAC命令所对应的 MACCommandSpec
// AppendDownlink encodes downlink MAC command cmd, appends it to b and returns any errors encountered.
func (spec MACCommandSpec) AppendDownlink(phy band.Band, b []byte, cmd ttnpb.MACCommand) ([]byte, error) {
return spec.append(phy, b, false, cmd)
}
func (spec MACCommandSpec) append(phy band.Band, b []byte, isUplink bool, cmd ttnpb.MACCommand) ([]byte, error) {
desc, ok := spec[cmd.CID]
if !ok || desc == nil {
return nil, errUnknownMACCommand.WithAttributes("cid", fmt.Sprintf("0x%X", int32(cmd.CID)))
}
b = append(b, byte(cmd.CID))
var appender func(phy band.Band, b []byte, cmd ttnpb.MACCommand) ([]byte, error)
if isUplink {
appender = desc.AppendUplink
if appender == nil {
return nil, errMACCommandUplink.WithAttributes("cid", fmt.Sprintf("0x%X", int32(cmd.CID)))
}
} else {
appender = desc.AppendDownlink
if appender == nil {
return nil, errMACCommandDownlink.WithAttributes("cid", fmt.Sprintf("0x%X", int32(cmd.CID)))
}
}
b, err := appender(phy, b, cmd)
if err != nil {
return nil, errEncodingMACCommand.WithAttributes("cid", fmt.Sprintf("0x%X", int32(cmd.CID))).WithCause(err)
}
return b, nil
}
最后在 MACCommandSpec 里头完成 LoRaWAN 协议组帧,这个结构体相当重要,大部分逻辑都在这里头处理。
// DefaultMACCommands contains all the default MAC commands.
var DefaultMACCommands = MACCommandSpec{
ttnpb.CID_xxx: &MACCommandDescriptor{
AppendUplink:
UnmarshalUplink:
AppendDownlink:
UnmarshalDownlink:
},
...
generateDownlink
各个MAC命令生成缓存
缓存传递至dev.MACState.PendingRequests
spec.AppendDownlink
enqueueMACCommand
desc.AppendDownlink
在各个MAC命令对应的MACCommandSpec完成协议组帧
发布了251 篇原创文章 · 获赞 253 · 访问量 84万+