func (u *utilization) AddTx(downlink *pb_router.DownlinkMessage) error { var t time.Duration var err error if lorawan := downlink.ProtocolConfiguration.GetLorawan(); lorawan != nil { if lorawan.Modulation == pb_lorawan.Modulation_LORA { t, err = toa.ComputeLoRa(uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate) if err != nil { return err } } if lorawan.Modulation == pb_lorawan.Modulation_FSK { t, err = toa.ComputeFSK(uint(len(downlink.Payload)), int(lorawan.BitRate)) if err != nil { return err } } } if t == 0 { return nil } u.overallTx.Update(int64(t) / 1000) frequency := downlink.GatewayConfiguration.Frequency u.channelTxLock.Lock() defer u.channelTxLock.Unlock() if _, ok := u.channelTx[frequency]; !ok { u.channelTx[frequency] = metrics.NewEWMA1() } u.channelTx[frequency].Update(int64(t) / 1000) return nil }
// see interface func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error { ctx := s.ctx.WithField("Identifier", id) s.Lock() defer s.Unlock() if item, ok := s.items[id]; ok { item.payload = downlink if lorawan := downlink.GetProtocolConfiguration().GetLorawan(); lorawan != nil { var time time.Duration if lorawan.Modulation == pb_lorawan.Modulation_LORA { // Calculate max ToA time, _ = toa.ComputeLoRa( uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate, ) } if lorawan.Modulation == pb_lorawan.Modulation_FSK { // Calculate max ToA time, _ = toa.ComputeFSK( uint(len(downlink.Payload)), int(lorawan.BitRate), ) } item.length = uint32(time / 1000) } if time.Now().Before(item.deadlineAt) { // Schedule transmission before the Deadline go func() { waitTime := item.deadlineAt.Sub(time.Now()) ctx.WithField("Remaining", waitTime).Info("Scheduled downlink") <-time.After(waitTime) s.RLock() defer s.RUnlock() if s.downlink != nil { s.downlink <- item.payload } }() } else { go func() { s.RLock() defer s.RUnlock() if s.downlink != nil { overdue := time.Now().Sub(item.deadlineAt) if overdue < Deadline { // Immediately send it ctx.WithField("Overdue", overdue).Warn("Send Late Downlink") s.downlink <- item.payload } else { ctx.WithField("Overdue", overdue).Warn("Discard Late Downlink") } } else { ctx.Warn("Unable to send Downlink") } }() } return nil } return errors.NewErrNotFound(id) }
// Calculating the score for each downlink option; lower is better, 0 is best // If a score is over 1000, it may should not be used as feasible option. // TODO: The weights of these parameters should be optimized. I'm sure someone // can do some computer simulations to find the right values. func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, options []*pb_broker.DownlinkOption) { gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing region := gatewayStatus.Region if region == "" { region = band.Guess(uplink.GatewayMetadata.Frequency) } gatewayRx, _ := gateway.Utilization.Get() for _, option := range options { // Invalid if no LoRaWAN lorawan := option.GetProtocolConfig().GetLorawan() if lorawan == nil { option.Score = 1000 continue } var time time.Duration if lorawan.Modulation == pb_lorawan.Modulation_LORA { // Calculate max ToA time, _ = toa.ComputeLoRa( 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? lorawan.DataRate, lorawan.CodingRate, ) } if lorawan.Modulation == pb_lorawan.Modulation_FSK { // Calculate max ToA time, _ = toa.ComputeFSK( 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? int(lorawan.BitRate), ) } // Invalid if time is zero if time == 0 { option.Score = 1000 continue } timeScore := math.Min(time.Seconds()*5, 10) // 2 seconds will be 10 (max) signalScore := 0.0 // Between 0 and 20 (lower is better) { // Prefer high SNR if uplink.GatewayMetadata.Snr < 5 { signalScore += 10 } // Prefer good RSSI signalScore += math.Min(float64(uplink.GatewayMetadata.Rssi*-0.1), 10) } utilizationScore := 0.0 // Between 0 and 40 (lower is better) will be over 100 if forbidden { // Avoid gateways that do more Rx utilizationScore += math.Min(gatewayRx*50, 20) / 2 // 40% utilization = 10 (max) // Avoid busy channels freq := option.GatewayConfig.Frequency channelRx, channelTx := gateway.Utilization.GetChannel(freq) utilizationScore += math.Min((channelTx+channelRx)*200, 20) / 2 // 10% utilization = 10 (max) // European Duty Cycle if region == "EU_863_870" { var duty float64 switch { case freq >= 863000000 && freq < 868000000: duty = 0.01 // g 863.0 – 868.0 MHz 1% case freq >= 868000000 && freq < 868600000: duty = 0.01 // g1 868.0 – 868.6 MHz 1% case freq >= 868700000 && freq < 869200000: duty = 0.001 // g2 868.7 – 869.2 MHz 0.1% case freq >= 869400000 && freq < 869650000: duty = 0.1 // g3 869.4 – 869.65 MHz 10% case freq >= 869700000 && freq < 870000000: duty = 0.01 // g4 869.7 – 870.0 MHz 1% default: utilizationScore += 100 // Transmissions on this frequency are forbidden } if channelTx > duty { utilizationScore += 100 // Transmissions on this frequency are forbidden } if duty > 0 { utilizationScore += math.Min(time.Seconds()/duty/100, 20) // Impact on duty-cycle (in order to prefer RX2 for SF9BW125) } } } scheduleScore := 0.0 // Between 0 and 30 (lower is better) will be over 100 if forbidden { id, conflicts := gateway.Schedule.GetOption(option.GatewayConfig.Timestamp, uint32(time/1000)) option.Identifier = id if conflicts >= 100 { scheduleScore += 100 } else { scheduleScore += math.Min(float64(conflicts*10), 30) // max 30 } } option.Score = uint32((timeScore + signalScore + utilizationScore + scheduleScore) * 10) } }