func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation bool, gateway *gateway.Gateway) (downlinkOptions []*pb_broker.DownlinkOption) { var options []*pb_broker.DownlinkOption gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing lorawanMetadata := uplink.ProtocolMetadata.GetLorawan() if lorawanMetadata == nil { return // We can't handle any other protocols than LoRaWAN yet } region := gatewayStatus.Region if region == "" { region = band.Guess(uplink.GatewayMetadata.Frequency) } band, err := band.Get(region) if err != nil { return // We can't handle this region } if region == "EU_863_870" && isActivation { band.RX2DataRate = 0 } dataRate, err := lorawanMetadata.GetDataRate() if err != nil { return } // Configuration for RX2 buildRX2 := func() (*pb_broker.DownlinkOption, error) { option := r.buildDownlinkOption(gateway.ID, band) if region == "EU_863_870" { option.GatewayConfig.Power = 27 // The EU RX2 frequency allows up to 27dBm } if isActivation { option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.JoinAcceptDelay2/1000) } else { option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.ReceiveDelay2/1000) } option.ProtocolConfig.GetLorawan().CodingRate = lorawanMetadata.CodingRate return option, nil } if option, err := buildRX2(); err == nil { options = append(options, option) } // Configuration for RX1 buildRX1 := func() (*pb_broker.DownlinkOption, error) { option := r.buildDownlinkOption(gateway.ID, band) if isActivation { option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.JoinAcceptDelay1/1000) } else { option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.ReceiveDelay1/1000) } option.ProtocolConfig.GetLorawan().CodingRate = lorawanMetadata.CodingRate freq, err := band.GetRX1Frequency(int(uplink.GatewayMetadata.Frequency)) if err != nil { return nil, err } option.GatewayConfig.Frequency = uint64(freq) upDR, err := band.GetDataRate(dataRate) if err != nil { return nil, err } downDR, err := band.GetRX1DataRate(upDR, 0) if err != nil { return nil, err } if err := option.ProtocolConfig.GetLorawan().SetDataRate(band.DataRates[downDR]); err != nil { return nil, err } option.GatewayConfig.FrequencyDeviation = uint32(option.ProtocolConfig.GetLorawan().BitRate / 2) return option, nil } if option, err := buildRX1(); err == nil { options = append(options, option) } computeDownlinkScores(gateway, uplink, options) for _, option := range options { // Add router ID to downlink option if r.Component != nil && r.Component.Identity != nil { option.Identifier = fmt.Sprintf("%s:%s", r.Component.Identity.Id, option.Identifier) } // Filter all illegal options if option.Score < 1000 { downlinkOptions = append(downlinkOptions, option) } } return }
func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { ctx := r.Ctx.WithFields(log.Fields{ "GatewayID": gatewayID, "AppEUI": *activation.AppEui, "DevEUI": *activation.DevEui, }) start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle activation") } else { ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") } }() r.status.activations.Mark(1) gateway := r.getGateway(gatewayID) gateway.LastSeen = time.Now() uplink := &pb.UplinkMessage{ Payload: activation.Payload, ProtocolMetadata: activation.ProtocolMetadata, GatewayMetadata: activation.GatewayMetadata, } if err = gateway.HandleUplink(uplink); err != nil { return nil, err } if !gateway.Schedule.IsActive() { return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayID)) } downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) // Find Broker brokers, err := r.Discovery.GetAll("broker") if err != nil { return nil, err } // Prepare request request := &pb_broker.DeviceActivationRequest{ Payload: activation.Payload, DevEui: activation.DevEui, AppEui: activation.AppEui, ProtocolMetadata: activation.ProtocolMetadata, GatewayMetadata: activation.GatewayMetadata, ActivationMetadata: &pb_protocol.ActivationMetadata{ Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ AppEui: activation.AppEui, DevEui: activation.DevEui, }, }, }, DownlinkOptions: downlinkOptions, } // Prepare LoRaWAN activation status, err := gateway.Status.Get() if err != nil { return nil, err } region := status.Region if region == "" { region = band.Guess(uplink.GatewayMetadata.Frequency) } band, err := band.Get(region) if err != nil { return nil, err } lorawan := request.ActivationMetadata.GetLorawan() lorawan.Rx1DrOffset = 0 lorawan.Rx2Dr = uint32(band.RX2DataRate) lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds()) if band.CFList != nil { lorawan.CfList = new(pb_lorawan.CFList) for _, freq := range band.CFList { lorawan.CfList.Freq = append(lorawan.CfList.Freq, freq) } } ctx = ctx.WithField("NumBrokers", len(brokers)) // Forward to all brokers and collect responses var wg sync.WaitGroup responses := make(chan *pb_broker.DeviceActivationResponse, len(brokers)) for _, broker := range brokers { broker, err := r.getBroker(broker) if err != nil { continue } // Do async request wg.Add(1) go func() { res, err := broker.client.Activate(r.Component.GetContext(""), request) if err == nil && res != nil { responses <- res } wg.Done() }() } // Make sure to close channel when all requests are done go func() { wg.Wait() close(responses) }() var gotFirst bool for res := range responses { if gotFirst { ctx.Warn("Duplicate Activation Response") } else { gotFirst = true downlink := &pb_broker.DownlinkMessage{ Payload: res.Payload, Message: res.Message, DownlinkOption: res.DownlinkOption, } err := r.HandleDownlink(downlink) if err != nil { ctx.Warn("Could not send downlink for Activation") gotFirst = false // try again } } } // Activation not accepted by any broker if !gotFirst { ctx.Debug("Activation not accepted at this gateway") return nil, errors.New("Activation not accepted at this Gateway") } // Activation accepted by (at least one) broker ctx.Debug("Activation accepted") return &pb.DeviceActivationResponse{}, nil }
// 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) } }