Skip to content

Commit

Permalink
Merge pull request #7286 from TheThingsNetwork/fix/ttigw-rx2
Browse files Browse the repository at this point in the history
Support TX channel configuration in dynamic channel regions
  • Loading branch information
johanstokking authored Sep 9, 2024
2 parents d259f3f + dae449f commit c35471d
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 83 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ For details about compatibility between different releases, see the **Commitment

### Fixed

- RX2 and Class C with The Things Industries gateway protocol in dynamic channel plan regions (including `EU868`).

### Security

## [3.32.0] - unreleased
Expand Down
9 changes: 0 additions & 9 deletions config/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5129,15 +5129,6 @@
"file": "mapping.go"
}
},
"error:pkg/gatewayserver/io/ttigw:invalid_frequency": {
"translations": {
"en": "invalid frequency `{frequency}`"
},
"description": {
"package": "pkg/gatewayserver/io/ttigw",
"file": "mapping.go"
}
},
"error:pkg/gatewayserver/io/ttigw:invalid_gateway_eui": {
"translations": {
"en": "invalid gateway EUI"
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ require (
go.packetbroker.org/api/mapping/v2 v2.3.2
go.packetbroker.org/api/routing v1.9.2
go.packetbroker.org/api/v3 v3.17.1
go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd
go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240909122048-ff0bf82927c1
go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df
go.thethings.network/lorawan-application-payload v0.0.0-20220125153912-1198ff1e403e
go.thethings.network/lorawan-stack-legacy/v2 v2.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,8 @@ go.packetbroker.org/api/routing v1.9.2 h1:J4+4vYZxa60UWC70Y9yy7sktU7DXaAp9Q13Bfq
go.packetbroker.org/api/routing v1.9.2/go.mod h1:kd2K7gieDI35YfPA8/zDmLX3qiKPuXia/MA77BEAeUA=
go.packetbroker.org/api/v3 v3.17.1 h1:LcyFPUGqVubGWMvQ16tZlQIKd+noGx7urzEYhSLiEQA=
go.packetbroker.org/api/v3 v3.17.1/go.mod h1:6bVbdWAYLnvZ5kgXxA7GBQvZTN7vxI0DoF1Di1NoAT4=
go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd h1:FvD516hdD/iWqoS20SFdcoiUgwRJP3egNXNiN+Ux2d0=
go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd/go.mod h1:2+WsMwIunNLh22oauBzGL56JazE3UY34W1fstqEbacw=
go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240909122048-ff0bf82927c1 h1:HbMxAtwFZlW9Wb2ozplPyrEvHGPFMyA47PCanAyQZCY=
go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240909122048-ff0bf82927c1/go.mod h1:2+WsMwIunNLh22oauBzGL56JazE3UY34W1fstqEbacw=
go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df h1:IMSctPllwEtJLyM/1AWgBiN1NPwBKqEVkCiK0I/MNY4=
go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df/go.mod h1:89OU623VYKW9i3W4CZgIGFmtgb/jsN8JV2PAuCsj+7w=
go.thethings.network/lorawan-application-payload v0.0.0-20220125153912-1198ff1e403e h1:TWGQ3lh7gI2W5hnb6qPdpoAa0d7s/XPwvgf2VVCMJaY=
Expand Down
42 changes: 38 additions & 4 deletions pkg/gatewayserver/io/iotest/iotest.go
Original file line number Diff line number Diff line change
Expand Up @@ -1508,7 +1508,7 @@ func Frontend(t *testing.T, frontend FrontendConfig) { //nolint:gocyclo
},
Priority: ttnpb.TxSchedulePriority_NORMAL,
Rx1Delay: ttnpb.RxDelay_RX_DELAY_1,
Rx1DataRate: &ttnpb.DataRate{
Rx2DataRate: &ttnpb.DataRate{
Modulation: &ttnpb.DataRate_Lora{
Lora: &ttnpb.LoRaDataRate{
SpreadingFactor: 7,
Expand All @@ -1517,7 +1517,7 @@ func Frontend(t *testing.T, frontend FrontendConfig) { //nolint:gocyclo
},
},
},
Rx1Frequency: 868100000,
Rx2Frequency: 869525000,
FrequencyPlanId: test.EUFrequencyPlanID,
},
},
Expand All @@ -1543,7 +1543,7 @@ func Frontend(t *testing.T, frontend FrontendConfig) { //nolint:gocyclo
},
Priority: ttnpb.TxSchedulePriority_NORMAL,
Rx1Delay: ttnpb.RxDelay_RX_DELAY_1,
Rx1DataRate: &ttnpb.DataRate{
Rx2DataRate: &ttnpb.DataRate{
Modulation: &ttnpb.DataRate_Lora{
Lora: &ttnpb.LoRaDataRate{
SpreadingFactor: 7,
Expand All @@ -1552,7 +1552,41 @@ func Frontend(t *testing.T, frontend FrontendConfig) { //nolint:gocyclo
},
},
},
Rx1Frequency: 868100000,
Rx2Frequency: 869525000,
},
},
},
},
{
Name: "ValidClassCWithCustomFrequency",
Message: &ttnpb.DownlinkMessage{
RawPayload: randomDownDataPayload(types.DevAddr{0x26, 0x02, 0xff, 0xff}, 42, 2),
Settings: &ttnpb.DownlinkMessage_Request{
Request: &ttnpb.TxRequest{
Class: ttnpb.Class_CLASS_C,
DownlinkPaths: []*ttnpb.DownlinkPath{
{
Path: &ttnpb.DownlinkPath_Fixed{
Fixed: &ttnpb.GatewayAntennaIdentifiers{
GatewayIds: &ttnpb.GatewayIdentifiers{
GatewayId: registeredGatewayID,
},
},
},
},
},
Priority: ttnpb.TxSchedulePriority_NORMAL,
Rx1Delay: ttnpb.RxDelay_RX_DELAY_1,
Rx2DataRate: &ttnpb.DataRate{
Modulation: &ttnpb.DataRate_Lora{
Lora: &ttnpb.LoRaDataRate{
SpreadingFactor: 7,
Bandwidth: 250000,
CodingRate: band.Cr4_5,
},
},
},
Rx2Frequency: 868123000,
},
},
},
Expand Down
131 changes: 79 additions & 52 deletions pkg/gatewayserver/io/ttigw/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ var (
"downlink_channel_mixed_bandwidths",
"downlink channel `{channel}` has mixed bandwidths `{bandwidth_low}` and `{bandwidth_high}` Hz",
)
errNotScheduled = errors.DefineInvalidArgument("not_scheduled", "downlink message not scheduled")
errInvalidFrequency = errors.DefineInvalidArgument("invalid_frequency", "invalid frequency `{frequency}`")
errNotScheduled = errors.DefineInvalidArgument("not_scheduled", "downlink message not scheduled")
)

const eirpDelta = 2.15
Expand Down Expand Up @@ -68,11 +67,18 @@ func gatewayStatusFromClientHello(clientHello *lorav1.ClientHelloNotification) *
return res
}

var bandwidthFromHz = map[uint32]lorav1.Bandwidth{
125000: lorav1.Bandwidth_BANDWIDTH_125_KHZ,
250000: lorav1.Bandwidth_BANDWIDTH_250_KHZ,
500000: lorav1.Bandwidth_BANDWIDTH_500_KHZ,
}
var (
toBandwidth = map[lorav1.Bandwidth]uint32{
lorav1.Bandwidth_BANDWIDTH_125_KHZ: 125000,
lorav1.Bandwidth_BANDWIDTH_250_KHZ: 250000,
lorav1.Bandwidth_BANDWIDTH_500_KHZ: 500000,
}
fromBandwidth = map[uint32]lorav1.Bandwidth{
125000: lorav1.Bandwidth_BANDWIDTH_125_KHZ,
250000: lorav1.Bandwidth_BANDWIDTH_250_KHZ,
500000: lorav1.Bandwidth_BANDWIDTH_500_KHZ,
}
)

func buildLoRaGatewayConfig(fp *frequencyplans.FrequencyPlan) (*lorav1.GatewayConfig, error) {
phy, err := band.GetLatest(fp.BandID)
Expand Down Expand Up @@ -136,7 +142,7 @@ func buildLoRaGatewayConfig(fp *frequencyplans.FrequencyPlan) (*lorav1.GatewayCo
int64(fp.LoRaStandardChannel.Frequency) - int64(fp.Radios[fp.LoRaStandardChannel.Radio].Frequency),
),
SpreadingFactor: dataRate.SpreadingFactor,
Bandwidth: bandwidthFromHz[dataRate.Bandwidth],
Bandwidth: fromBandwidth[dataRate.Bandwidth],
}
}
}
Expand Down Expand Up @@ -169,7 +175,15 @@ func buildLoRaGatewayConfig(fp *frequencyplans.FrequencyPlan) (*lorav1.GatewayCo
}
tx = append(tx, &lorav1.TransmitChannel{
Frequency: ch.Frequency,
Bandwidth: bandwidthFromHz[drLow.Bandwidth],
Bandwidth: fromBandwidth[drLow.Bandwidth],
})
}
// Add the default RX2 frequency and bandwidth if it fits.
// The frequency plan's overrides are only relevant to end devices, so only the default RX2 parameters are considered.
if rx2DR := phy.DataRates[phy.DefaultRx2Parameters.DataRateIndex].Rate.GetLora(); len(tx) < 16 && rx2DR != nil {
tx = append(tx, &lorav1.TransmitChannel{
Frequency: phy.DefaultRx2Parameters.Frequency,
Bandwidth: fromBandwidth[rx2DR.Bandwidth],
})
}

Expand All @@ -195,19 +209,17 @@ var (
)

func toUplinkMessage(
ids *ttnpb.GatewayIdentifiers, fp *frequencyplans.FrequencyPlan, msg *lorav1.UplinkMessage,
ids *ttnpb.GatewayIdentifiers, gtwConf *lorav1.GatewayConfig, msg *lorav1.UplinkMessage,
) (*ttnpb.UplinkMessage, error) {
if msg.Board != 0 {
return nil, errInvalidBoard.WithAttributes("board", msg.Board)
}
phy, err := band.GetLatest(fp.BandID)
if err != nil {
return nil, err
}
board := gtwConf.Boards[0]
var (
frequency uint64
dataRate = &ttnpb.DataRate{}
rxMetadata = &ttnpb.RxMetadata{
rfChain uint32
frequencyOffset int32
dataRate = &ttnpb.DataRate{}
rxMetadata = &ttnpb.RxMetadata{
GatewayIds: ids,
Timestamp: msg.Timestamp,
Rssi: -msg.RssiChannelNegated,
Expand All @@ -216,10 +228,18 @@ func toUplinkMessage(
)
switch {
case msg.IfChain < 8: // LoRa multi-SF
if int(msg.IfChain) >= len(fp.UplinkChannels) {
return nil, errInvalidIFChain.WithAttributes("if_chain", msg.IfChain)
}
frequency = fp.UplinkChannels[msg.IfChain].Frequency
multipleSF := []*lorav1.Board_IntermediateFrequencies_MultipleSF{
board.Ifs.MultipleSf0,
board.Ifs.MultipleSf1,
board.Ifs.MultipleSf2,
board.Ifs.MultipleSf3,
board.Ifs.MultipleSf4,
board.Ifs.MultipleSf5,
board.Ifs.MultipleSf6,
board.Ifs.MultipleSf7,
}[msg.IfChain]
rfChain = multipleSF.RfChain
frequencyOffset = multipleSF.Frequency
modulation := msg.GetLora()
if modulation == nil {
return nil, errInvalidModulation.New()
Expand All @@ -241,31 +261,25 @@ func toUplinkMessage(
}

case msg.IfChain == 8: // FSK
if fp.FSKChannel == nil {
return nil, errInvalidIFChain.WithAttributes("if_chain", msg.IfChain)
}
dr := phy.DataRates[ttnpb.DataRateIndex(fp.FSKChannel.DataRate)].Rate.GetFsk()
frequency = fp.FSKChannel.Frequency
rfChain = board.Ifs.Fsk.RfChain
frequencyOffset = board.Ifs.Fsk.Frequency
dataRate.Modulation = &ttnpb.DataRate_Fsk{
Fsk: &ttnpb.FSKDataRate{
BitRate: dr.BitRate,
BitRate: board.Ifs.Fsk.Bitrate,
},
}

case msg.IfChain == 9: // LoRa standard channel
if fp.LoRaStandardChannel == nil {
return nil, errInvalidIFChain.WithAttributes("if_chain", msg.IfChain)
}
dr := phy.DataRates[ttnpb.DataRateIndex(fp.LoRaStandardChannel.DataRate)].Rate.GetLora()
frequency = fp.LoRaStandardChannel.Frequency
rfChain = board.Ifs.LoraServiceChannel.RfChain
frequencyOffset = board.Ifs.LoraServiceChannel.Frequency
modulation := msg.GetLora()
if modulation == nil {
return nil, errInvalidModulation.New()
}
dataRate.Modulation = &ttnpb.DataRate_Lora{
Lora: &ttnpb.LoRaDataRate{
SpreadingFactor: dr.SpreadingFactor,
Bandwidth: dr.Bandwidth,
SpreadingFactor: board.Ifs.LoraServiceChannel.SpreadingFactor,
Bandwidth: toBandwidth[board.Ifs.LoraServiceChannel.Bandwidth],
CodingRate: toCodingRate[modulation.CodeRate],
},
}
Expand All @@ -282,6 +296,8 @@ func toUplinkMessage(
return nil, errInvalidIFChain.WithAttributes("if_chain", msg.IfChain)
}

centerFrequency := []*lorav1.Board_RFChain{board.RfChain0, board.RfChain1}[rfChain].Frequency
frequency := uint64(int64(centerFrequency) + int64(frequencyOffset))
return &ttnpb.UplinkMessage{
RawPayload: msg.Payload,
Settings: &ttnpb.TxSettings{
Expand All @@ -293,35 +309,38 @@ func toUplinkMessage(
}, nil
}

func fromDownlinkMessage(
fp *frequencyplans.FrequencyPlan, msg *ttnpb.DownlinkMessage,
) (*lorav1.DownlinkMessage, error) {
func fromDownlinkMessage(gtwConf *lorav1.GatewayConfig, msg *ttnpb.DownlinkMessage) (*lorav1.DownlinkMessage, error) {
scheduled := msg.GetScheduled()
if scheduled == nil || scheduled.Downlink == nil {
return nil, errNotScheduled.New()
}
var (
txChannel uint32
found bool
)
for i, ch := range fp.DownlinkChannels {
if ch.Frequency == scheduled.Frequency {
txChannel = uint32(i)
found = true
break
}
}
if !found {
return nil, errInvalidFrequency.WithAttributes("frequency", scheduled.Frequency)
}
res := &lorav1.DownlinkMessage{
TxPower: uint32(scheduled.Downlink.TxPower - eirpDelta),
TxChannel: txChannel,
Timestamp: scheduled.Timestamp,
Payload: msg.RawPayload,
}
if dr := scheduled.DataRate.GetLora(); dr != nil {
// Only LoRa transmission channels are configured via gateway config so that TxChannelIndex can be used.
// FSK is always configured via TxChannelConfig, see below.
for i, ch := range gtwConf.Tx {
if ch.Frequency == scheduled.Frequency && ch.Bandwidth == fromBandwidth[dr.Bandwidth] {
res.TxChannel = &lorav1.DownlinkMessage_TxChannelIndex{
TxChannelIndex: uint32(i),
}
break
}
}
}
switch mod := scheduled.DataRate.Modulation.(type) {
case *ttnpb.DataRate_Lora:
if res.TxChannel == nil {
res.TxChannel = &lorav1.DownlinkMessage_TxChannelConfig{
TxChannelConfig: &lorav1.TransmitChannel{
Frequency: scheduled.Frequency,
Bandwidth: fromBandwidth[mod.Lora.Bandwidth],
},
}
}
res.DataRate = &lorav1.DownlinkMessage_Lora_{
Lora: &lorav1.DownlinkMessage_Lora{
SpreadingFactor: mod.Lora.SpreadingFactor,
Expand All @@ -330,6 +349,14 @@ func fromDownlinkMessage(
},
}
case *ttnpb.DataRate_Fsk:
if res.TxChannel == nil {
res.TxChannel = &lorav1.DownlinkMessage_TxChannelConfig{
TxChannelConfig: &lorav1.TransmitChannel{
Frequency: scheduled.Frequency,
Bandwidth: lorav1.Bandwidth_BANDWIDTH_125_KHZ,
},
}
}
res.DataRate = &lorav1.DownlinkMessage_Fsk{
Fsk: &lorav1.DownlinkMessage_FSK{
Bitrate: mod.Fsk.BitRate,
Expand Down
Loading

0 comments on commit c35471d

Please sign in to comment.