nextDataDownlinkSlot returns the next downlinkSlot before or at earliestAt when next data downlink can be transmitted to the device given the data known by Network Server and true, if such downlinkSlot and downlink exist, otherwise it returns nil and false.
( // nolint: gocyclo ctx context.Context, dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings, earliestAt time.Time, profile *ttnpb.MACSettings, )
| 240 | // nextDataDownlinkSlot returns the next downlinkSlot before or at earliestAt when next data downlink can be transmitted to the device |
| 241 | // given the data known by Network Server and true, if such downlinkSlot and downlink exist, otherwise it returns nil and false. |
| 242 | func nextDataDownlinkSlot( // nolint: gocyclo |
| 243 | ctx context.Context, |
| 244 | dev *ttnpb.EndDevice, |
| 245 | phy *band.Band, |
| 246 | defaults *ttnpb.MACSettings, |
| 247 | earliestAt time.Time, |
| 248 | profile *ttnpb.MACSettings, |
| 249 | ) (downlinkSlot, bool) { |
| 250 | if dev.GetMacState() == nil { |
| 251 | return nil, false |
| 252 | } |
| 253 | earliestAt = latestTime(earliestAt, time.Now()) |
| 254 | if dev.MacState.LastDownlinkAt != nil { |
| 255 | earliestAt = latestTime(earliestAt, *ttnpb.StdTime(dev.MacState.LastDownlinkAt)) |
| 256 | } |
| 257 | logger := log.FromContext(ctx).WithField("earliest_at", earliestAt) |
| 258 | |
| 259 | var needsAck bool |
| 260 | classA, hasClassA := lastClassADataDownlinkSlot(dev, phy) |
| 261 | if hasClassA { |
| 262 | switch classA.Uplink.Payload.MHdr.MType { |
| 263 | case ttnpb.MType_UNCONFIRMED_UP: |
| 264 | if classA.Uplink.Payload.GetMacPayload().FHdr.FCtrl.AdrAckReq { |
| 265 | logger.Debug("Acknowledgment required for ADRAckReq") |
| 266 | needsAck = dev.MacState.LastDownlinkAt == nil || ttnpb.StdTime(dev.MacState.LastDownlinkAt).Before(*ttnpb.StdTime(classA.Uplink.ReceivedAt)) |
| 267 | } |
| 268 | case ttnpb.MType_CONFIRMED_UP: |
| 269 | logger.Debug("Acknowledgment required for confirmed uplink") |
| 270 | needsAck = dev.MacState.LastDownlinkAt == nil || ttnpb.StdTime(dev.MacState.LastDownlinkAt).Before(*ttnpb.StdTime(classA.Uplink.ReceivedAt)) |
| 271 | } |
| 272 | rx2 := classA.RX2() |
| 273 | switch hasClassA = dev.MacState.RxWindowsAvailable && !rx2.Before(earliestAt) && deviceHasPathForDownlink(ctx, dev, nil); { |
| 274 | case !hasClassA: |
| 275 | case len(dev.MacState.QueuedResponses) > 0: |
| 276 | logger.Debug("MAC responses enqueued, choose class A downlink slot") |
| 277 | return classA, true |
| 278 | case mac.ContainsStickyMACCommand(dev.MacState.RecentMacCommandIdentifiers...): |
| 279 | logger.Debug("Sticky MAC response received, choose class A downlink slot") |
| 280 | return classA, true |
| 281 | case dev.MacState.PendingRelayDownlink != nil: |
| 282 | logger.Debug("Pending relay downlink, choose class A downlink slot") |
| 283 | return classA, true |
| 284 | case mac.DeviceNeedsADRParamSetupReq(dev, phy): |
| 285 | logger.Debug("Device needs ADRParamSetupReq, choose class A downlink slot") |
| 286 | return classA, true |
| 287 | case mac.DeviceNeedsBeaconFreqReq(dev): |
| 288 | logger.Debug("Device needs BeaconFreqReq, choose class A downlink slot") |
| 289 | return classA, true |
| 290 | case mac.DeviceNeedsBeaconTimingReq(dev): |
| 291 | logger.Debug("Device needs BeaconTimingReq, choose class A downlink slot") |
| 292 | return classA, true |
| 293 | case mac.DeviceNeedsDevStatusReq(dev, defaults, rx2): |
| 294 | logger.Debug("Device needs DevStatusReq, choose class A downlink slot") |
| 295 | return classA, true |
| 296 | case mac.DeviceNeedsDLChannelReq(dev, phy): |
| 297 | logger.Debug("Device needs DLChannelReq, choose class A downlink slot") |
| 298 | return classA, true |
| 299 | case mac.DeviceNeedsDutyCycleReq(dev): |