diff --git a/cmd/docs/README.md b/cmd/docs/README.md index 5ef8c4264..d9287aa8f 100644 --- a/cmd/docs/README.md +++ b/cmd/docs/README.md @@ -147,6 +147,7 @@ ttn gen-keypair generates a public/private keypair **Options** ``` + --force-adr-optimize Force ADR optimization --net-id int LoRaWAN NetID (default 19) --redis-address string Redis server and port (default "localhost:6379") --redis-db int Redis database diff --git a/cmd/networkserver.go b/cmd/networkserver.go index d91d20a84..6095b0964 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -116,6 +116,9 @@ func init() { "26000000/20": "otaa,abp,world,local,private,testing", }) + networkserverCmd.Flags().Bool("force-adr-optimize", false, "Force ADR optimization") + viper.BindPFlag("networkserver.force-adr-optimize", networkserverCmd.Flags().Lookup("force-adr-optimize")) + networkserverCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") networkserverCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") networkserverCmd.Flags().Int("server-port", 1903, "The port for communication") diff --git a/core/networkserver/adr.go b/core/networkserver/adr.go index 1e332a060..fd3e64926 100644 --- a/core/networkserver/adr.go +++ b/core/networkserver/adr.go @@ -4,7 +4,6 @@ package networkserver import ( - "math" "sort" pb_broker "github.com/TheThingsNetwork/api/broker" @@ -15,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" + "github.com/spf13/viper" ) // DefaultADRMargin is the default SNR margin for ADR @@ -33,15 +33,6 @@ func maxSNR(frames []*device.Frame) float32 { return max } -func lossPercentage(frames []*device.Frame) int { - if len(frames) == 0 { - return 0 - } - sentPackets := frames[0].FCnt - frames[len(frames)-1].FCnt + 1 - loss := sentPackets - uint32(len(frames)) - return int(math.Floor((float64(loss) / float64(sentPackets) * 100) + .5)) -} - const ScheduleMACEvent = "schedule mac command" func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMessage, dev *device.Device) error { @@ -75,57 +66,74 @@ func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMes ctx.WithError(err).Error("Could not push frame for device") } - frames, _ := history.Get() - - if dev.ADR.Failed <= maxADRFails && len(frames) >= device.FramesHistorySize { - lorawanDownlinkMAC.ADR = true - } - md := message.GetProtocolMetadata() if dev.ADR.Band == "" { dev.ADR.Band = md.GetLoRaWAN().GetFrequencyPlan().String() } + if dev.ADR.Margin == 0 { + dev.ADR.Margin = DefaultADRMargin + } - var scheduleADR, forceADR bool + fp, err := band.Get(dev.ADR.Band) + if err != nil { + return err + } + dev.ADR.DataRate = md.GetLoRaWAN().GetDataRate() + if dev.ADR.TxPower == 0 { + dev.ADR.TxPower = fp.DefaultTXPower + } + if dev.ADR.NbTrans == 0 { + dev.ADR.NbTrans = 1 + } + dev.ADR.SendReq = false - switch dev.ADR.Band { - case pb_lorawan.FrequencyPlan_US_902_928.String(), pb_lorawan.FrequencyPlan_AU_915_928.String(): - if !dev.ADR.SentInitial { - scheduleADR = true - forceADR = true - message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "initial") - ctx.Debug("Schedule ADR [initial]") - } + adrMargin := float32(dev.ADR.Margin) + frames, _ := history.Get() + if len(frames) >= device.FramesHistorySize { + frames = frames[:device.FramesHistorySize] + } else { + adrMargin += 2.5 } - dataRate := md.GetLoRaWAN().GetDataRate() - if dev.ADR.DataRate != dataRate { - dev.ADR.DataRate = dataRate - scheduleADR = true - message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "optimize") - ctx.Debug("Schedule ADR [optimize]") + desiredDataRate, desiredTxPower, err := fp.ADRSettings(dev.ADR.DataRate, dev.ADR.TxPower, maxSNR(frames), adrMargin) + if err == band.ErrADRUnavailable { + ctx.Debugf("ADR not available in %s", dev.ADR.Band) + return nil + } + if err != nil { + return err } - if lorawanUplinkMAC.ADRAckReq { - scheduleADR = true + var forceADR bool + + if !dev.ADR.SentInitial && (dev.ADR.Band == pb_lorawan.FrequencyPlan_US_902_928.String() || dev.ADR.Band == pb_lorawan.FrequencyPlan_AU_915_928.String()) { + dev.ADR.SendReq = true + forceADR = true + message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "initial") + ctx.Debug("Schedule ADR [initial]") + } else if lorawanUplinkMAC.ADRAckReq { + dev.ADR.SendReq = true + forceADR = true + message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "adr-ack-req") lorawanDownlinkMAC.Ack = true - message.Trace = message.Trace.WithEvent("set ack", "reason", "adr-ack-req") ctx.Debug("Schedule ADR [adr-ack-req]") + } else if dev.ADR.DataRate != desiredDataRate || dev.ADR.TxPower != desiredTxPower { + dev.ADR.SendReq = true + if drIdx, err := fp.GetDataRateIndexFor(dev.ADR.DataRate); err == nil && drIdx == 0 { + forceADR = true + } else { + forceADR = viper.GetBool("networkserver.force-adr-optimize") + } + message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "optimize") + ctx.Debugf("Schedule ADR [optimize] %s->%s", dev.ADR.DataRate, desiredDataRate) } - if scheduleADR { - if fp, err := band.Get(dev.ADR.Band); err == nil { - if drIdx, err := fp.GetDataRateIndexFor(dataRate); err == nil && drIdx == 0 { - if len(frames) >= device.FramesHistorySize { - forceADR = true - message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "avoid high sf") - ctx.Debug("Schedule ADR [avoid high sf]") - } - } - } + if !dev.ADR.SendReq { + return nil } - dev.ADR.SendReq = scheduleADR + dev.ADR.DataRate, dev.ADR.TxPower, dev.ADR.NbTrans = desiredDataRate, desiredTxPower, 1 + if forceADR { err := n.setADR(lorawanDownlinkMAC, dev) if err != nil { @@ -138,7 +146,7 @@ func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMes return nil } -const maxADRFails = 3 +const maxADRFails = 10 func (n *networkServer) setADR(mac *pb_lorawan.MACPayload, dev *device.Device) error { if !dev.ADR.SendReq { @@ -166,90 +174,19 @@ func (n *networkServer) setADR(mac *pb_lorawan.MACPayload, dev *device.Device) e ctx.Debug("Empty ADR DataRate") return nil } - if dev.ADR.Margin == 0 { - dev.ADR.Margin = DefaultADRMargin - } - if dev.ADR.Band == "" { - ctx.Debug("Empty ADR Band") - return nil - } - fp, err := band.Get(dev.ADR.Band) - if err != nil { - return err - } - if dev.ADR.TxPower == 0 { - dev.ADR.TxPower = fp.DefaultTXPower - } - if dev.ADR.NbTrans == 0 { - dev.ADR.NbTrans = 1 - } - - // Get history - history, err := n.devices.Frames(dev.AppEUI, dev.DevEUI) - if err != nil { - return err - } - frames, err := history.Get() - if err != nil { - return err - } - switch { - case len(frames) == 0: - ctx.Debug("No historical data for ADR") - return nil - case len(frames) >= device.FramesHistorySize: - frames = frames[:device.FramesHistorySize] - } - - // Add extra margin if we don't have enough data yet - adrMargin := float32(dev.ADR.Margin) - if len(frames) < device.FramesHistorySize { - adrMargin += 2.5 - } - // Calculate desired ADR settings - dataRate, txPower, err := fp.ADRSettings(dev.ADR.DataRate, dev.ADR.TxPower, maxSNR(frames), adrMargin) - if err == band.ErrADRUnavailable { - ctx.Debugf("ADR not available in %s", dev.ADR.Band) - return nil - } + fp, err := band.Get(dev.ADR.Band) if err != nil { return err } - drIdx, err := fp.GetDataRateIndexFor(dataRate) + drIdx, err := fp.GetDataRateIndexFor(dev.ADR.DataRate) if err != nil { return err } - powerIdx, err := fp.GetTxPowerIndexFor(txPower) + powerIdx, err := fp.GetTxPowerIndexFor(dev.ADR.TxPower) if err != nil { powerIdx, _ = fp.GetTxPowerIndexFor(fp.DefaultTXPower) } - var nbTrans = dev.ADR.NbTrans - if dev.ADR.DataRate == dataRate && dev.ADR.TxPower == txPower && !dev.Options.DisableFCntCheck { - lossPercentage := lossPercentage(frames) - switch { - case lossPercentage <= 5: - nbTrans-- - case lossPercentage <= 10: - // don't change - case lossPercentage <= 30: - nbTrans++ - default: - nbTrans += 2 - } - if nbTrans < 1 { - nbTrans = 1 - } - if nbTrans > 3 { - nbTrans = 3 - } - } - - if dev.ADR.SentInitial && dev.ADR.DataRate == dataRate && dev.ADR.TxPower == txPower && dev.ADR.NbTrans == nbTrans { - ctx.Debug("No ADR needed") - return nil // Nothing to do - } - dev.ADR.DataRate, dev.ADR.TxPower, dev.ADR.NbTrans = dataRate, txPower, nbTrans payloads := getAdrReqPayloads(dev, &fp, drIdx, powerIdx) if len(payloads) == 0 { diff --git a/core/networkserver/adr_test.go b/core/networkserver/adr_test.go index 49ae599a7..8e620d27e 100644 --- a/core/networkserver/adr_test.go +++ b/core/networkserver/adr_test.go @@ -5,7 +5,6 @@ package networkserver import ( "math" - "runtime" "sort" "testing" @@ -31,7 +30,7 @@ func adrInitUplinkMessage() *pb_broker.DeduplicatedUplinkMessage { downlink.Message.InitLoRaWAN().InitDownlink() message.ProtocolMetadata = pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_LoRaWAN{ LoRaWAN: &pb_lorawan.Metadata{ - DataRate: "SF8BW125", + DataRate: "SF10BW125", }, }} message.GatewayMetadata = []*pb_gateway.RxMetadata{ @@ -71,380 +70,200 @@ func TestMaxSNR(t *testing.T) { a.So(maxSNR(buildFrames(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), ShouldEqual, 9.8) } -func TestLossPercentage(t *testing.T) { - a := New(t) - a.So(lossPercentage(buildFrames()), ShouldEqual, 0) - a.So(lossPercentage(buildFrames(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), ShouldEqual, 0) - a.So(lossPercentage(buildFrames(1, 2, 3, 4, 5, 6, 7, 8, 9, 11)), ShouldEqual, 9) // 1/11 missing - a.So(lossPercentage(buildFrames(1, 2, 3, 4, 5, 6, 7, 8, 9, 12)), ShouldEqual, 17) // 2/12 missing - a.So(lossPercentage(buildFrames(1, 2, 3, 6, 7, 8, 9, 12, 13, 14)), ShouldEqual, 29) // 4/14 missing -} - -func TestHandleUplinkADR(t *testing.T) { - a := New(t) +func TestADR(t *testing.T) { ns := &networkServer{ Component: &component.Component{ - Ctx: GetLogger(t, "TestHandleUplink"), + Ctx: GetLogger(t, "TestADR"), }, - devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-uplink-adr"), + devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-adr"), } ns.InitStatus() defer func() { - keys, _ := GetRedisClient().Keys("*ns-test-handle-uplink-adr*").Result() + keys, _ := GetRedisClient().Keys("*ns-test-adr*").Result() for _, key := range keys { GetRedisClient().Del(key).Result() } }() - appEUI := types.AppEUI([8]byte{1}) - devEUI := types.DevEUI([8]byte{1}) - history, _ := ns.devices.Frames(appEUI, devEUI) - - // Setting ADR to true should start collecting frames - { - dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI} - message := adrInitUplinkMessage() - message.Message.GetLoRaWAN().GetMACPayload().ADR = true - err := ns.handleUplinkADR(message, dev) - a.So(err, ShouldBeNil) - frames, _ := history.Get() - a.So(frames, ShouldHaveLength, 1) - a.So(dev.ADR.DataRate, ShouldEqual, "SF8BW125") - } - - // Resetting ADR to false should empty the frames - { - dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI} - message := adrInitUplinkMessage() - err := ns.handleUplinkADR(message, dev) - a.So(err, ShouldBeNil) - frames, _ := history.Get() - a.So(frames, ShouldBeEmpty) - } - - // Setting ADRAckReq to true should set the ACK and schedule a LinkADRReq - { - dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI} - message := adrInitUplinkMessage() - message.Message.GetLoRaWAN().GetMACPayload().ADR = true - message.Message.GetLoRaWAN().GetMACPayload().ADRAckReq = true - err := ns.handleUplinkADR(message, dev) - a.So(err, ShouldBeNil) - resMAC := message.ResponseTemplate.Message.GetLoRaWAN().GetMACPayload() - a.So(resMAC.Ack, ShouldBeTrue) - a.So(dev.ADR.SendReq, ShouldBeTrue) - } -} - -func TestHandleDownlinkADR(t *testing.T) { - a := New(t) - ns := &networkServer{ - Component: &component.Component{ - Ctx: GetLogger(t, "TestHandleUplink"), + for i, tt := range []struct { + Name string + Band string + DesiredInitialDataRate string + DesiredInitialDataRateIndex int + DesiredInitialTxPower int + DesiredInitialTxPowerIndex int + DesiredDataRate string + DesiredDataRateIndex int + DesiredTxPower int + DesiredTxPowerIndex int + }{ + { + Name: "EU Device", Band: "EU_863_870", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 4, + DesiredInitialTxPower: 14, DesiredInitialTxPowerIndex: 1, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 5, + DesiredTxPower: 14, DesiredTxPowerIndex: 1, }, - devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-downlink-adr"), - } - ns.InitStatus() - - defer func() { - keys, _ := GetRedisClient().Keys("*ns-test-handle-downlink-adr*").Result() - for _, key := range keys { - GetRedisClient().Del(key).Result() - } - }() - - appEUI := types.AppEUI([8]byte{1}) - devEUI := types.DevEUI([8]byte{1}) - history, _ := ns.devices.Frames(appEUI, devEUI) - dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI} - dev.ADR.SentInitial = true - - message := adrInitDownlinkMessage() - - var shouldReturnError = func() { - a := New(t) - message = adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - a.So(message.Message.GetLoRaWAN().GetMACPayload().FOpts, ShouldHaveLength, 1) - if a.Failed() { - _, file, line, _ := runtime.Caller(1) - t.Errorf("\n%s:%d", file, line) - } - } - var nothingShouldHappen = func() { - a := New(t) - message = adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - a.So(message.Message.GetLoRaWAN().GetMACPayload().FOpts, ShouldHaveLength, 1) - if a.Failed() { - _, file, line, _ := runtime.Caller(1) - t.Errorf("\n%s:%d", file, line) - } - } - - // initially - nothingShouldHappen() - - dev.ADR.SendReq = true - nothingShouldHappen() + { + Name: "AS Device", Band: "AS_923", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 4, + DesiredInitialTxPower: 14, DesiredInitialTxPowerIndex: 0, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 5, + DesiredTxPower: 14, DesiredTxPowerIndex: 0, + }, + { + Name: "AS1 Device", Band: "AS_920_923", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 4, + DesiredInitialTxPower: 14, DesiredInitialTxPowerIndex: 0, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 5, + DesiredTxPower: 14, DesiredTxPowerIndex: 0, + }, + { + Name: "AS2 Device", Band: "AS_923_925", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 4, + DesiredInitialTxPower: 14, DesiredInitialTxPowerIndex: 0, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 5, + DesiredTxPower: 14, DesiredTxPowerIndex: 0, + }, + { + Name: "KR Device", Band: "KR_920_923", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 4, + DesiredInitialTxPower: 14, DesiredInitialTxPowerIndex: 1, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 5, + DesiredTxPower: 14, DesiredTxPowerIndex: 1, + }, + { + Name: "RU Device", Band: "RU_864_870", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 4, + DesiredInitialTxPower: 14, DesiredInitialTxPowerIndex: 1, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 5, + DesiredTxPower: 14, DesiredTxPowerIndex: 1, + }, + { + Name: "US Device", Band: "US_902_928", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 2, + DesiredInitialTxPower: 20, DesiredInitialTxPowerIndex: 5, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 3, + DesiredTxPower: 20, DesiredTxPowerIndex: 5, + }, + { + Name: "AU Device", Band: "AU_915_928", + DesiredInitialDataRate: "SF8BW125", DesiredInitialDataRateIndex: 4, + DesiredInitialTxPower: 20, DesiredInitialTxPowerIndex: 5, + DesiredDataRate: "SF7BW125", DesiredDataRateIndex: 5, + DesiredTxPower: 20, DesiredTxPowerIndex: 5, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + a := New(t) + + appEUI := types.AppEUI([8]byte{1}) + devEUI := types.DevEUI([8]byte{1, uint8(i)}) + dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI} + + history, _ := ns.devices.Frames(appEUI, devEUI) + + uplink, downlink := adrInitUplinkMessage(), adrInitDownlinkMessage() + uplink.ProtocolMetadata.GetLoRaWAN().FrequencyPlan = pb_lorawan.FrequencyPlan(pb_lorawan.FrequencyPlan_value[tt.Band]) + + err := ns.handleUplinkADR(uplink, dev) + a.So(err, ShouldBeNil) + a.So(dev.ADR.SendReq, ShouldBeFalse) + + uplink.Message.GetLoRaWAN().GetMACPayload().ADR = true + + err = ns.handleUplinkADR(uplink, dev) + a.So(err, ShouldBeNil) + a.So(dev.ADR.SendReq, ShouldBeTrue) + a.So(dev.ADR.DataRate, ShouldEqual, tt.DesiredInitialDataRate) + a.So(dev.ADR.TxPower, ShouldEqual, tt.DesiredInitialTxPower) + + frames, err := history.Get() + a.So(err, ShouldBeNil) + a.So(frames, ShouldHaveLength, 1) + + err = ns.handleDownlinkADR(downlink, dev) + a.So(err, ShouldBeNil) + + a.So(dev.ADR.SentInitial, ShouldBeTrue) + + fOpts := downlink.Message.GetLoRaWAN().GetMACPayload().FOpts + fOpt := fOpts[1] + if tt.Band == "US_902_928" || tt.Band == "AU_915_928" { + fOpt = fOpts[2] + + var req lorawan.LinkADRReqPayload + req.UnmarshalBinary(fOpts[1].Payload) + + switch tt.Band { + case "US_902_928": + a.So(req.DataRate, ShouldEqual, 4) // 500kHz channel, so DR4 + case "AU_915_928": + a.So(req.DataRate, ShouldEqual, 6) // 500kHz channel, so DR6 + } + + a.So(req.TXPower, ShouldEqual, 0) // Max tx power + a.So(req.Redundancy.ChMaskCntl, ShouldEqual, 7) // Ch 64-71, All 125 kHz channels off + a.So(req.ChMask[0], ShouldBeFalse) // Channel 64 disabled + a.So(req.ChMask[1], ShouldBeTrue) // Channel 65 enabled + for i := 2; i < 8; i++ { // Channels 66-71 disabled + a.So(req.ChMask[i], ShouldBeFalse) + } + } - var resetFrames = func(appEUI types.AppEUI, devEUI types.DevEUI) { - history.Clear() - for i := 0; i < 20; i++ { - history.Push(&device.Frame{SNR: 10, GatewayCount: 3, FCnt: uint32(i)}) - } - } - resetFrames(dev.AppEUI, dev.DevEUI) - - nothingShouldHappen() - - dev.ADR.DataRate = "SF8BW125" - nothingShouldHappen() - - dev.ADR.Band = "INVALID" - shouldReturnError() - - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 20 - - { - dev.ADR.Band = "US_902_928" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 3) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) // First LinkAdrReq - a.So(payload.DataRate, ShouldEqual, 4) // 500kHz channel, so DR4 - a.So(payload.TXPower, ShouldEqual, 0) // Max tx power - a.So(payload.Redundancy.ChMaskCntl, ShouldEqual, 7) - // Ch 64-71, All 125 kHz channels off - a.So(payload.ChMask[0], ShouldBeFalse) // Channel 64 disabled - a.So(payload.ChMask[1], ShouldBeTrue) // Channel 65 enabled - for i := 2; i < 8; i++ { // Channels 66-71 disabled - a.So(payload.ChMask[i], ShouldBeFalse) - } - payload = new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[2].Payload) // Second LinkAdrReq - a.So(payload.DataRate, ShouldEqual, 3) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 5) // 20 - a.So(payload.Redundancy.ChMaskCntl, ShouldEqual, 0) // Channels 0..15 - for i := 0; i < 8; i++ { // First 8 channels disabled - a.So(payload.ChMask[i], ShouldBeFalse) - } - for i := 8; i < 16; i++ { // Second 8 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - } + a.So(fOpt.CID, ShouldEqual, lorawan.LinkADRReq) + var req lorawan.LinkADRReqPayload + req.UnmarshalBinary(fOpt.Payload) + + a.So(req.DataRate, ShouldEqual, tt.DesiredInitialDataRateIndex) + a.So(req.TXPower, ShouldEqual, tt.DesiredInitialTxPowerIndex) + + if tt.Band == "US_902_928" || tt.Band == "AU_915_928" { + a.So(req.Redundancy.ChMaskCntl, ShouldEqual, 0) // Channels 0..15 + for i := 0; i < 8; i++ { // First 8 channels disabled + a.So(req.ChMask[i], ShouldBeFalse) + } + for i := 8; i < 16; i++ { // Second 8 channels enabled + a.So(req.ChMask[i], ShouldBeTrue) + } + } - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 20 - - { - dev.ADR.Band = "AU_915_928" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 3) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) // First LinkAdrReq - a.So(payload.DataRate, ShouldEqual, 6) // 500kHz channel, so DR6 - a.So(payload.TXPower, ShouldEqual, 0) // Max tx power - a.So(payload.Redundancy.ChMaskCntl, ShouldEqual, 7) - // Ch 64-71, All 125 kHz channels off - a.So(payload.ChMask[0], ShouldBeFalse) // Channel 64 disabled - a.So(payload.ChMask[1], ShouldBeTrue) // Channel 65 enabled - for i := 2; i < 8; i++ { // Channels 66-71 disabled - a.So(payload.ChMask[i], ShouldBeFalse) - } - payload = new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[2].Payload) // Second LinkAdrReq - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 5) // 20 - a.So(payload.Redundancy.ChMaskCntl, ShouldEqual, 0) // Channels 0..15 - for i := 0; i < 8; i++ { // First 8 channels disabled - a.So(payload.ChMask[i], ShouldBeFalse) - } - for i := 8; i < 16; i++ { // Second 8 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - } + for i := 0; i < 20; i++ { + uplink.Message.GetLoRaWAN().GetMACPayload().FCnt++ + ns.handleUplinkADR(uplink, dev) + } - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 20 - - { - dev.ADR.Band = "EU_863_870" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 2) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 1) // 14 - for i := 0; i < 8; i++ { // First 8 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - a.So(payload.ChMask[8], ShouldBeFalse) // 9th channel (FSK) disabled - } + frames, err = history.Get() + a.So(err, ShouldBeNil) + a.So(frames, ShouldHaveLength, 20) - ns.Component.Ctx.Info("Start AS ADR Test") - - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 10 - - { - dev.ADR.Band = "AS_923" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 2) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 2) // 10 - for i := 0; i < 1; i++ { // First 2 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - for i := 2; i < 8; i++ { // Next 6 channels disabled - a.So(payload.ChMask[i], ShouldBeFalse) - } - } + a.So(dev.ADR.SendReq, ShouldBeTrue) + a.So(dev.ADR.DataRate, ShouldEqual, tt.DesiredDataRate) + a.So(dev.ADR.TxPower, ShouldEqual, tt.DesiredTxPower) - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 10 - - { - dev.ADR.Band = "AS_920_923" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 2) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 2) // 10 - for i := 0; i < 8; i++ { // First 8 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - } + err = ns.handleDownlinkADR(downlink, dev) + a.So(err, ShouldBeNil) - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 10 - - { - dev.ADR.Band = "AS_923_925" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 2) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 2) // 10 - for i := 0; i < 8; i++ { // First 8 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - } + fOpts = downlink.Message.GetLoRaWAN().GetMACPayload().FOpts + fOpt = fOpts[1] + if tt.Band == "US_902_928" || tt.Band == "AU_915_928" { + fOpt = fOpts[2] + } - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 10 - - { - dev.ADR.Band = "KR_920_923" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 2) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 2) // 10 - for i := 0; i < 7; i++ { // First 7 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - a.So(payload.ChMask[7], ShouldBeFalse) // 8th channel disabled - } + a.So(fOpt.CID, ShouldEqual, lorawan.LinkADRReq) + req.UnmarshalBinary(fOpt.Payload) - dev.ADR.DataRate = "SF10BW125" - dev.ADR.TxPower = 20 - - { - dev.ADR.Band = "RU_864_870" - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 2) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 1) // 14 - for i := 0; i < 7; i++ { // First 7 channels enabled - a.So(payload.ChMask[i], ShouldBeTrue) - } - a.So(payload.ChMask[7], ShouldBeFalse) // 8th channel disabled - } + a.So(req.DataRate, ShouldEqual, tt.DesiredDataRateIndex) + a.So(req.TXPower, ShouldEqual, tt.DesiredTxPowerIndex) - shouldHaveNbTrans := func(nbTrans int) { - a := New(t) - message := adrInitDownlinkMessage() - err := ns.handleDownlinkADR(message, dev) - a.So(err, ShouldBeNil) - fOpts := message.Message.GetLoRaWAN().GetMACPayload().FOpts - a.So(fOpts, ShouldHaveLength, 2) - a.So(fOpts[1].CID, ShouldEqual, lorawan.LinkADRReq) - payload := new(lorawan.LinkADRReqPayload) - payload.UnmarshalBinary(fOpts[1].Payload) - a.So(payload.DataRate, ShouldEqual, 5) // SF7BW125 - a.So(payload.TXPower, ShouldEqual, 1) // 14 - a.So(payload.Redundancy.NbRep, ShouldEqual, nbTrans) - if a.Failed() { - _, file, line, _ := runtime.Caller(1) - t.Errorf("\n%s:%d", file, line) - } - } + uplink.ProtocolMetadata.GetLoRaWAN().DataRate = tt.DesiredDataRate + uplink.GatewayMetadata[0].SNR = 7.5 - tests := map[int]map[int]int{ - 1: map[int]int{0: 1, 1: 1, 2: 1, 4: 2, 10: 3}, - 2: map[int]int{0: 1, 1: 1, 2: 2, 4: 3, 10: 3}, - 3: map[int]int{0: 2, 1: 2, 2: 3, 4: 3, 10: 3}, - } + err = ns.handleUplinkADR(uplink, dev) + a.So(err, ShouldBeNil) - for nbTrans, test := range tests { - for loss, exp := range test { - dev.ADR.NbTrans = nbTrans - resetFrames(dev.AppEUI, dev.DevEUI) - history.Push(&device.Frame{SNR: 10, GatewayCount: 3, FCnt: uint32(20 + loss)}) - if nbTrans == exp { - nothingShouldHappen() - } else { - shouldHaveNbTrans(exp) - } - } + a.So(dev.ADR.SendReq, ShouldBeFalse) + }) } - - // Invalid case - message = adrInitDownlinkMessage() - dev.ADR.DataRate = "INVALID" - shouldReturnError() } diff --git a/core/networkserver/uplink_test.go b/core/networkserver/uplink_test.go index c80a797d5..334223f7b 100644 --- a/core/networkserver/uplink_test.go +++ b/core/networkserver/uplink_test.go @@ -112,8 +112,14 @@ func TestHandleUplink(t *testing.T) { // ResponseTemplate should ACK the ADRACKReq a.So(macPayload.FHDR.FCtrl.ACK, ShouldBeTrue) - a.So(macPayload.FHDR.FOpts, ShouldHaveLength, 1) + a.So(macPayload.FHDR.FOpts, ShouldHaveLength, 2) a.So(macPayload.FHDR.FOpts[0].Payload, ShouldResemble, &lorawan.LinkCheckAnsPayload{GwCnt: 1, Margin: 7}) + a.So(macPayload.FHDR.FOpts[1].Payload, ShouldResemble, &lorawan.LinkADRReqPayload{ + DataRate: 5, + TXPower: 1, + ChMask: [16]bool{true, true, true, true, true, true, true, true}, + Redundancy: lorawan.Redundancy{NbRep: 1}, + }) // Frame Counter should have been updated dev, _ := ns.devices.Get(appEUI, devEUI)