Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indicate whether the frequency plan is applicable to gateways #7235

Merged
merged 9 commits into from
Sep 5, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ For details about compatibility between different releases, see the **Commitment

### Added

- Option to filter out non-gateway related frequency plans.
- `ListFrequencyPlans` RPC has a new `gateways-only` flag.

### Changed

### Deprecated
Expand Down
1 change: 1 addition & 0 deletions api/ttn/lorawan/v3/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,7 @@ PeerInfo
| ----- | ---- | ----- | ----------- |
| `base_frequency` | [`uint32`](#uint32) | | Optional base frequency in MHz for hardware support (433, 470, 868 or 915) |
| `band_id` | [`string`](#string) | | Optional Band ID to filter the results. |
| `gateways_only` | [`bool`](#bool) | | Optional field to filter out the non-gateway related results. |

### <a name="ttn.lorawan.v3.ListFrequencyPlansResponse">Message `ListFrequencyPlansResponse`</a>

Expand Down
7 changes: 7 additions & 0 deletions api/ttn/lorawan/v3/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4730,6 +4730,13 @@
"in": "query",
"required": false,
"type": "string"
},
{
"name": "gateways_only",
"description": "Optional field to filter out the non-gateway related results.",
"in": "query",
"required": false,
"type": "boolean"
}
],
"tags": [
Expand Down
2 changes: 2 additions & 0 deletions api/ttn/lorawan/v3/configuration_services.proto
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ message ListFrequencyPlansRequest {
uint32 base_frequency = 1;
// Optional Band ID to filter the results.
string band_id = 2;
// Optional field to filter out the non-gateway related results.
bool gateways_only = 3;
halimi marked this conversation as resolved.
Show resolved Hide resolved
}

message FrequencyPlanDescription {
Expand Down
14 changes: 13 additions & 1 deletion cmd/ttn-lw-cli/commands/gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ func getGatewayEUI(flagSet *pflag.FlagSet, args []string, requireEUI bool) (*ttn
return ids, nil
}

func listFrequencyPlansFlags() *pflag.FlagSet {
flagSet := &pflag.FlagSet{}
flagSet.Uint32("base-frequency", 0, "Base frequency in MHz for hardware support (433, 470, 868 or 915)")
flagSet.String("band-id", "", "Band ID to filter by")
flagSet.Bool("gateways-only", false, "List only frequency plans that support gateways")
return flagSet
}

var (
gatewaysCommand = &cobra.Command{
Use: "gateways",
Expand All @@ -118,12 +126,16 @@ var (
PersistentPreRunE: preRun(),
RunE: func(cmd *cobra.Command, args []string) error {
baseFrequency, _ := cmd.Flags().GetUint32("base-frequency")
bandID, _ := cmd.Flags().GetString("band-id")
gatewaysOnly, _ := cmd.Flags().GetBool("gateways-only")
gs, err := api.Dial(ctx, config.GatewayServerGRPCAddress)
if err != nil {
return err
}
res, err := ttnpb.NewConfigurationClient(gs).ListFrequencyPlans(ctx, &ttnpb.ListFrequencyPlansRequest{
BaseFrequency: baseFrequency,
BandId: bandID,
GatewaysOnly: gatewaysOnly,
})
if err != nil {
return err
Expand Down Expand Up @@ -610,7 +622,7 @@ If both the parameter and the flag are provided, the flag is ignored.`,

func init() {
ttnpb.AddSelectFlagsForGateway(selectGatewayFlags, "", false)
gatewaysListFrequencyPlans.Flags().Uint32("base-frequency", 0, "Base frequency in MHz for hardware support (433, 470, 868 or 915)")
gatewaysListFrequencyPlans.Flags().AddFlagSet(listFrequencyPlansFlags())
gatewaysCommand.AddCommand(gatewaysListFrequencyPlans)
ttnpb.AddSetFlagsForListGatewaysRequest(gatewaysListCommand.Flags(), "", false)
AddCollaboratorFlagAlias(gatewaysListCommand.Flags(), "collaborator")
Expand Down
32 changes: 32 additions & 0 deletions pkg/frequencyplans/frequencyplans.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ type FrequencyPlan struct {
DefaultRx2DataRate *uint8 `yaml:"rx2-default-data-rate,omitempty"`
// MaxEIRP is the maximum EIRP as ceiling for any (sub-)band value.
MaxEIRP *float32 `yaml:"max-eirp,omitempty"`
// Gateways is a boolean indicating whether the frequency plan is suitable for gateways.
Gateways *bool `yaml:"gateways,omitempty"`
}

// Extend returns the same frequency plan, with values overridden by the passed frequency plan.
Expand Down Expand Up @@ -412,6 +414,11 @@ func (fp FrequencyPlan) Extend(extension FrequencyPlan) FrequencyPlan {
val := *extension.MaxEIRP
extended.MaxEIRP = &val
}
if extension.Gateways != nil {
val := *extension.Gateways
extended.Gateways = &val
}

return extended
}

Expand Down Expand Up @@ -728,3 +735,28 @@ func (s *Store) GetAllIDs() ([]string, error) {

return ids, nil
}

// GetGatewayFrequencyPlans returns the list of frequency plans that are suitable for gateways.
func (s *Store) GetGatewayFrequencyPlans() ([]*FrequencyPlan, error) {
if s == nil {
return nil, errNotConfigured.New()
}

descriptions, err := s.descriptions()
if err != nil {
return nil, errReadList.WithCause(err)
}

var gatewayFrequencyPlans []*FrequencyPlan
for _, description := range descriptions {
frequencyPlan, err := s.getByID(description.ID)
if err != nil {
return nil, err
}
if frequencyPlan.Gateways != nil && *frequencyPlan.Gateways {
gatewayFrequencyPlans = append(gatewayFrequencyPlans, frequencyPlan)
}
}

return gatewayFrequencyPlans, nil
}
68 changes: 68 additions & 0 deletions pkg/frequencyplans/frequencyplans_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,71 @@ dwell-time:
})
}
}

func TestGetGatewayFrequencyPlans(t *testing.T) {
t.Parallel()
a := assertions.New(t)

store := frequencyplans.NewStore(fetch.NewMemFetcher(map[string][]byte{
"frequency-plans.yml": []byte(`- id: EU_863_870
band-id: EU_863_870
name: Europe 863-870 MHz (SF12 for RX2)
description: Default frequency plan for Europe
base-frequency: 868
file: EU_863_870.yml
- id: EU_863_870_TTN
band-id: EU_863_870
name: Europe 863-870 MHz (SF9 for RX2 - recommended)
description: TTN Community Network frequency plan for Europe, using SF9 for RX2
base-frequency: 868
base-id: EU_863_870
file: EU_863_870_TTN.yml
- id: US_902_928_FSB_1
band-id: US_902_928
name: United States 902-928 MHz, FSB 1
description: Default frequency plan for the United States and Canada, using sub-band 1
base-frequency: 915
file: US_902_928_FSB_1.yml
- id: AS_923_2
band-id: AS_923_2
name: Asia 920-923 MHz (AS923 Group 2) with only default channels
description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band
base-frequency: 915
file: AS_923_2.yml
- id: AS_923_2_DT
band-id: AS_923_2
name: Asia 920-923 MHz (AS923 Group 2) with only default channels and dwell time enabled
base-frequency: 915
base-id: AS_923_2
file: enable_dwell_time_400ms.yml
`),
"EU_863_870.yml": []byte(`band-id: EU_863_870
max-eirp: 1
gateways: false
`),
"EU_863_870_TTN.yml": []byte(`max-eirp: 2
gateways: true
`),
"US_902_928_FSB_1.yml": []byte(`band-id: US_902_928
max-eirp: 3
`),
"AS_923_2.yml": []byte(`band-id: AS_923_2
max-eirp: 4
gateways: true
`),
"enable_dwell_time_400ms.yml": []byte(`max-eirp: 5
gateways: false
`),
}))

// Frequency plan with gateways
{
plans, err := store.GetGatewayFrequencyPlans()
a.So(err, should.BeNil)
a.So(plans, should.HaveLength, 2)
a.So(plans[0].BandID, should.Equal, "EU_863_870")
a.So(plans[1].BandID, should.Equal, "AS_923_2")
a.So(*plans[0].MaxEIRP, should.Equal, 2)
a.So(*plans[1].MaxEIRP, should.Equal, 4)
}
}
9 changes: 9 additions & 0 deletions pkg/frequencyplans/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ func (s *RPCServer) ListFrequencyPlans(ctx context.Context, req *ttnpb.ListFrequ
if req.BandId != "" && req.BandId != desc.BandID {
continue
}
if req.GatewaysOnly {
frequencyPlan, err := s.store.getByID(desc.ID)
if err != nil {
return nil, err
}
if frequencyPlan.Gateways == nil || !*frequencyPlan.Gateways {
continue
}
}
res.FrequencyPlans = append(res.FrequencyPlans, &ttnpb.FrequencyPlanDescription{
Id: desc.ID,
BandId: desc.BandID,
Expand Down
17 changes: 17 additions & 0 deletions pkg/frequencyplans/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ func TestRPCServer(t *testing.T) {
description: Frequency Plan C
base-frequency: 915
file: C.yml`),
"A.yml": []byte(`band-id: EU_863_870
max-eirp: 1
gateways: false
`),
"B.yml": []byte(`max-eirp: 2
gateways: true
`),
"C.yml": []byte(`band-id: US_902_928
max-eirp: 3
`),
}))

server := frequencyplans.NewRPCServer(store)
Expand Down Expand Up @@ -86,4 +96,11 @@ func TestRPCServer(t *testing.T) {
a.So(err, should.BeNil)
a.So(bandAS.FrequencyPlans, should.HaveLength, 1)
a.So(bandAS.FrequencyPlans[0], should.Resemble, expectedAll[1])

gateways, err := server.ListFrequencyPlans(context.Background(), &ttnpb.ListFrequencyPlansRequest{
GatewaysOnly: true,
})
a.So(err, should.BeNil)
a.So(gateways.FrequencyPlans, should.HaveLength, 1)
a.So(bandAS.FrequencyPlans[0], should.Resemble, expectedAll[1])
}
Loading
Loading