The Things Network LoRaWAN Stack V3 学习笔记 3.1.2.2 下行 MAC 命令处理流程

前言

MAC 命令下行,目前和数据下行一样,主要也是在 generateDownlink() 进行处理。

小能手这段时间在学习 The Things Network LoRaWAN Stack V3,从使用和代码等角度对该 Stack 进行了分析,详细可点此查看

1 代码流程梳理

1.1 MAC 命令准备

	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.MACCommandCID + Payload
pld.MACCommand()*MACCommandCID + Payload
cmd.GetLinkADRReq()*MACCommand_LinkADRReqPayload
pld*MACCommand_LinkADRReqPayload

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:"-"`
}

1.2 MAC 命令组帧并下发

前头准备好的各类 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:
	},
	...

2 代码流程图

generateDownlink
各个MAC命令生成缓存
缓存传递至dev.MACState.PendingRequests
spec.AppendDownlink
enqueueMACCommand
desc.AppendDownlink
在各个MAC命令对应的MACCommandSpec完成协议组帧

END


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值