diff --git a/docs/frequency-plans.md b/docs/frequency-plans.md
new file mode 100644
index 0000000..754bd33
--- /dev/null
+++ b/docs/frequency-plans.md
@@ -0,0 +1,34 @@
+# LoRaWAN Frequency Plans for The Things Stack
+
+
+## [EU_863_870](../end-device/EU_863_870.yml): Europe 863-870 MHz
+
+
+>> Default frequency plan for Europe
+
+![EU_863_870](images/end-device/EU_863_870.svg)
+
+
+
+
+
+## `EU_863_870_TTN`: Europe 863-870 MHz
+Based on [EU_863_870](##EU_863_870) and modified by [rx2_default_data_rata_3.yml](../end-device/modifiers/rx2_default_data_rata_3.yml)
+
+
+>> TTN Community Network frequency plan for Europe, using SF9 for RX2
+
+![EU_863_870_TTN](images/end-device/EU_863_870_TTN.svg)
+
+
+
+
+
+## [EU_863_870](../gateway/EU_863_870.yml): Europe 863-870 MHz
+
+
+>> Default frequency plan for Europe
+
+![EU_863_870](images/gateway/EU_863_870.svg)
+
+
diff --git a/docs/images/end-device/EU_863_870.svg b/docs/images/end-device/EU_863_870.svg
new file mode 100644
index 0000000..1c36ed7
--- /dev/null
+++ b/docs/images/end-device/EU_863_870.svg
@@ -0,0 +1,271 @@
+
\ No newline at end of file
diff --git a/docs/images/end-device/EU_863_870_TTN.svg b/docs/images/end-device/EU_863_870_TTN.svg
new file mode 100644
index 0000000..e3684ea
--- /dev/null
+++ b/docs/images/end-device/EU_863_870_TTN.svg
@@ -0,0 +1,271 @@
+
\ No newline at end of file
diff --git a/docs/images/gateway/EU_863_870.svg b/docs/images/gateway/EU_863_870.svg
new file mode 100644
index 0000000..f516e19
--- /dev/null
+++ b/docs/images/gateway/EU_863_870.svg
@@ -0,0 +1,304 @@
+
\ No newline at end of file
diff --git a/internal/docs/docs.go b/internal/docs/docs.go
index 6d7e52b..fee2a18 100644
--- a/internal/docs/docs.go
+++ b/internal/docs/docs.go
@@ -3,16 +3,12 @@ package docs
import (
"bytes"
"embed"
+ "errors"
"fmt"
- "log"
"os"
- "sort"
"strings"
"text/template"
- "github.com/wcharczuk/go-chart/v2"
- "go.thethings.network/lorawan-stack/v3/pkg/frequencyplans"
-
"github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model"
)
@@ -23,35 +19,22 @@ var tmpl = template.Must(template.ParseFS(fsys, "*.tmpl"))
// Generate generates the documentation for the frequency-plans.
func Generate(sourceFile, destinationFolder string) error {
- plans := model.FrequencyPlanDescriptions{}
- output, err := plans.Parse(sourceFile)
+ output, err := model.FrequencyPlanDescriptions{}.Parse(sourceFile)
if err != nil {
return err
}
+ descriptions := output.(model.FrequencyPlanDescriptions)
- for _, plan := range output.(model.FrequencyPlanDescriptions).EndDeviceDescriptions {
- // TODO: Support extensions
- if plan.BaseID != nil {
- log.Printf("Skipping %s: extending a base plan not supported yet", plan.ID)
- continue
- }
- if err := renderEndDevice(plan.ID, *plan.File); err != nil {
- return err
- }
+ if err := renderPlans("gateway", descriptions.GatewayDescriptions, model.FrequencyPlanGateway{}); err != nil {
+ return err
}
- for _, plan := range output.(model.FrequencyPlanDescriptions).GatewayDescriptions {
- // TODO: Support extensions
- if plan.BaseID != nil {
- log.Printf("Skipping %s: extending a base plan not supported yet", plan.ID)
- continue
- }
- if err := renderEndDevice(plan.ID, *plan.File); err != nil {
- return err
- }
+
+ if err := renderPlans("end-device", descriptions.EndDeviceDescriptions, model.FrequencyPlanEndDevice{}); err != nil {
+ return err
}
var buf bytes.Buffer
- if err := tmpl.ExecuteTemplate(&buf, "frequency-plans.md.tmpl", plans); err != nil {
+ if err := tmpl.ExecuteTemplate(&buf, "frequency-plans.md.tmpl", output); err != nil {
return err
}
if err := os.WriteFile(destinationFolder+"/frequency-plans.md", buf.Bytes(), 0o644); err != nil {
@@ -64,195 +47,55 @@ func formatFrequency(f float64) string {
return strings.TrimRight(fmt.Sprintf("%.3f", f/1_000_000), "0.")
}
-func renderEndDevice(id, file string) error {
- plan := model.FrequencyPlanEndDevice{}
- _, err := plan.Parse("./end-device/" + file)
- if err != nil {
- return err
- }
-
- // return render(id, "./docs/images/end-device/", plan)
- return nil
-}
-
-func renderGateway(id, file string) error {
- plan := model.FrequencyPlanGateway{}
- _, err := plan.Parse("./gateway/" + file)
- if err != nil {
- return err
+func renderPlans(folder string, descriptions []model.FrequencyPlanDescription, definition model.Definition) error {
+ for _, plan := range descriptions {
+ fileName := ""
+ if plan.HasModifiers() {
+ for _, description := range descriptions {
+ if description.ID == *plan.BaseID {
+ fileName = *description.File
+ break
+ }
+ }
+ } else {
+ fileName = *plan.File
+ }
+ basePlan, err := definition.Parse(folder + "/" + fileName)
+ if err != nil {
+ return err
+ }
+ if plan.HasModifiers() {
+ for _, modifierName := range *plan.Modifiers {
+ switch definition.(type) {
+ case model.FrequencyPlanEndDevice:
+ modifier, err := model.FrequencyPlanEndDeviceModifier{}.Parse(folder + "/modifiers/" + modifierName)
+ if err != nil {
+ return err
+ }
+ basePlan = basePlan.(model.FrequencyPlanEndDevice).Modify(modifier.(model.FrequencyPlanEndDeviceModifier))
+ case model.FrequencyPlanGateway:
+ modifier, err := model.FrequencyPlanGatewayModifier{}.Parse(folder + "/modifiers/" + modifierName)
+ if err != nil {
+ return err
+ }
+ basePlan = basePlan.(model.FrequencyPlanGateway).Modify(modifier.(model.FrequencyPlanGatewayModifier))
+ }
+ }
+ }
+ if err := render(plan.ID, basePlan); err != nil {
+ return err
+ }
}
-
- // return render(id, "./docs/images/gateway/", plan)
return nil
}
-func render(id string, folder string, plan frequencyplans.FrequencyPlan) error {
- frequencies := make(map[float64]string)
-
- graph := chart.Chart{
- Title: id,
- Width: 1920,
- Height: 1080,
- DPI: 150,
- }
-
- annotations := chart.AnnotationSeries{}
-
- for _, ch := range plan.UplinkChannels {
- freq := float64(ch.Frequency)
- start, end := freq-62500, freq+62500
- frequencies[freq] = formatFrequency(freq)
- color := chart.GetDefaultColor(int(ch.Radio))
- color.A = 128
- graph.Series = append(graph.Series, chart.ContinuousSeries{
- Style: chart.Style{
- StrokeColor: color,
- FillColor: color,
- },
- XValues: []float64{start, end},
- YValues: []float64{float64(1), float64(1)},
- })
- annotations.Annotations = append(annotations.Annotations, chart.Value2{
- XValue: freq,
- YValue: 1,
- Label: formatFrequency(freq),
- })
- }
-
- if ch := plan.LoRaStandardChannel; ch != nil {
- freq := float64(ch.Frequency)
- start, end := freq-125000, freq+125000
- frequencies[freq] = formatFrequency(freq)
- color := chart.GetDefaultColor(int(ch.Radio))
- color.A = 128
- graph.Series = append(graph.Series, chart.ContinuousSeries{
- Style: chart.Style{
- StrokeColor: color,
- FillColor: color,
- },
- XValues: []float64{start, end},
- YValues: []float64{float64(2), float64(2)},
- })
- annotations.Annotations = append(annotations.Annotations, chart.Value2{
- XValue: freq,
- YValue: 2,
- Label: formatFrequency(freq) + " (Std)",
- })
- }
-
- if ch := plan.FSKChannel; ch != nil {
- freq := float64(ch.Frequency)
- start, end := freq-62500, freq+62500
- frequencies[freq] = formatFrequency(freq)
- color := chart.GetDefaultColor(int(ch.Radio))
- color.A = 128
- graph.Series = append(graph.Series, chart.ContinuousSeries{
- Style: chart.Style{
- StrokeColor: color,
- FillColor: color,
- },
- XValues: []float64{start, end},
- YValues: []float64{float64(2), float64(2)},
- })
- annotations.Annotations = append(annotations.Annotations, chart.Value2{
- XValue: freq,
- YValue: 2,
- Label: formatFrequency(freq) + " (FSK)",
- })
- }
-
- for _, ch := range plan.DownlinkChannels {
- freq := float64(ch.Frequency)
- start, end := freq-62500, freq+62500
- frequencies[freq] = formatFrequency(freq)
- color := chart.GetDefaultColor(3)
- color.A = 128
- graph.Series = append(graph.Series, chart.ContinuousSeries{
- Style: chart.Style{
- StrokeColor: color,
- FillColor: color,
- },
- XValues: []float64{start, end},
- YValues: []float64{float64(-1), float64(-1)},
- })
- annotations.Annotations = append(annotations.Annotations, chart.Value2{
- XValue: freq,
- YValue: -1,
- Label: formatFrequency(freq),
- })
- }
-
- for i, radio := range plan.Radios {
- freq := float64(radio.Frequency)
- start, end := freq-462500, freq+462500
- frequencies[start] = formatFrequency(start)
- frequencies[freq] = formatFrequency(freq)
- frequencies[end] = formatFrequency(end)
- color := chart.GetDefaultColor(i)
- color.A = 128
- graph.Series = append(graph.Series, chart.ContinuousSeries{
- Style: chart.Style{
- StrokeColor: color,
- StrokeWidth: 10,
- },
- XValues: []float64{start, end},
- YValues: []float64{float64(0), float64(0)},
- })
- annotations.Annotations = append(annotations.Annotations, chart.Value2{
- XValue: freq,
- YValue: 0,
- Label: fmt.Sprintf("Radio %d: %s", i, formatFrequency(freq)),
- })
- }
-
- var frequencySlice []float64
- for frequency := range frequencies {
- frequencySlice = append(frequencySlice, frequency)
- }
- sort.Float64s(frequencySlice)
-
- graph.XAxis = chart.XAxis{
- Range: &chart.ContinuousRange{
- Min: frequencySlice[0],
- Max: frequencySlice[len(frequencySlice)-1],
- },
- TickStyle: chart.Style{
- TextRotationDegrees: 45.0,
- },
+func render(id string, definition model.Definition) error {
+ switch mod := definition.(type) {
+ case model.FrequencyPlanGateway:
+ return renderGateway(id, mod)
+ case model.FrequencyPlanEndDevice:
+ return renderEndDevice(id, mod)
+ default:
+ return errors.New("unsupported type")
}
-
- for _, freq := range frequencySlice {
- graph.XAxis.Ticks = append(graph.XAxis.Ticks, chart.Tick{
- Value: freq,
- Label: frequencies[freq],
- })
- }
-
- graph.YAxis = chart.YAxis{
- Range: &chart.ContinuousRange{
- Min: -2,
- Max: 3,
- },
- Ticks: []chart.Tick{
- {Value: -2},
- {Value: -1, Label: "Downlink"},
- {Value: 0, Label: "Radio"},
- {Value: 1, Label: "Uplink"},
- {Value: 2, Label: "Std/FSK"},
- {Value: 3},
- },
- }
-
- graph.Series = append(graph.Series, annotations)
-
- var buf bytes.Buffer
-
- if err := graph.Render(chart.SVG, &buf); err != nil {
- return err
- }
- if err := os.WriteFile(folder+id+".svg", buf.Bytes(), 0o644); err != nil {
- return err
- }
-
- return nil
}
diff --git a/internal/docs/end-device.go b/internal/docs/end-device.go
new file mode 100644
index 0000000..393b074
--- /dev/null
+++ b/internal/docs/end-device.go
@@ -0,0 +1,156 @@
+package docs
+
+import (
+ "bytes"
+ "os"
+ "sort"
+
+ "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model"
+ "github.com/wcharczuk/go-chart/v2"
+)
+
+func renderEndDevice(id string, plan model.FrequencyPlanEndDevice) error {
+ frequencies := make(map[float64]string)
+
+ graph := chart.Chart{
+ Title: id,
+ Width: 1920,
+ Height: 1080,
+ DPI: 150,
+ }
+
+ annotations := chart.AnnotationSeries{}
+
+ for _, ch := range plan.Channels {
+ freq := float64(*ch.UplinkFrequency)
+ start, end := freq-62500, freq+62500
+ frequencies[freq] = formatFrequency(freq)
+ color := chart.GetDefaultColor(0)
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(1), float64(1)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: 1,
+ Label: formatFrequency(freq),
+ })
+
+ freq = float64(*ch.DownlinkFrequency)
+ start, end = freq-62500, freq+62500
+ frequencies[freq] = formatFrequency(freq)
+ color = chart.GetDefaultColor(3)
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(-1), float64(-1)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: -1,
+ Label: formatFrequency(freq),
+ })
+ }
+
+ if ch := plan.LoRaStandardChannel; ch != nil {
+ freq := float64(*ch.Frequency)
+ start, end := freq-125000, freq+125000
+ frequencies[freq] = formatFrequency(freq)
+ color := chart.GetDefaultColor(1)
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(2), float64(2)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: 2,
+ Label: formatFrequency(freq) + " (Std)",
+ })
+ }
+
+ if ch := plan.FSKChannel; ch != nil {
+ freq := float64(*ch.Frequency)
+ start, end := freq-62500, freq+62500
+ frequencies[freq] = formatFrequency(freq)
+ color := chart.GetDefaultColor(2)
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(2), float64(2)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: 2,
+ Label: formatFrequency(freq) + " (FSK)",
+ })
+ }
+
+ var frequencySlice []float64
+ for frequency := range frequencies {
+ frequencySlice = append(frequencySlice, frequency)
+ }
+ sort.Float64s(frequencySlice)
+
+ graph.XAxis = chart.XAxis{
+ Range: &chart.ContinuousRange{
+ Min: frequencySlice[0],
+ Max: frequencySlice[len(frequencySlice)-1],
+ },
+ TickStyle: chart.Style{
+ TextRotationDegrees: 45.0,
+ },
+ }
+
+ for _, freq := range frequencySlice {
+ graph.XAxis.Ticks = append(graph.XAxis.Ticks, chart.Tick{
+ Value: freq,
+ Label: frequencies[freq],
+ })
+ }
+
+ graph.YAxis = chart.YAxis{
+ Range: &chart.ContinuousRange{
+ Min: -2,
+ Max: 3,
+ },
+ Ticks: []chart.Tick{
+ {Value: -2},
+ {Value: -1, Label: "Downlink"},
+ {Value: 0, Label: "Radio"},
+ {Value: 1, Label: "Uplink"},
+ {Value: 2, Label: "Std/FSK"},
+ {Value: 3},
+ },
+ }
+
+ graph.Series = append(graph.Series, annotations)
+
+ var buf bytes.Buffer
+
+ if err := graph.Render(chart.SVG, &buf); err != nil {
+ return err
+ }
+ if err := os.WriteFile("./docs/images/end-device/"+id+".svg", buf.Bytes(), 0o644); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/internal/docs/frequency-plans.md.tmpl b/internal/docs/frequency-plans.md.tmpl
index a422d33..e0b27ae 100644
--- a/internal/docs/frequency-plans.md.tmpl
+++ b/internal/docs/frequency-plans.md.tmpl
@@ -1,11 +1,32 @@
# LoRaWAN Frequency Plans for The Things Stack
-{{- range . }}
+{{- range .EndDeviceDescriptions }}
+{{ if .BaseID }}
## `{{ .ID }}`: {{ .Name }}
+Based on [{{ .BaseID }}](##{{ .BaseID }}) and modified by {{ range .Modifiers }}[{{ . }}](../end-device/modifiers/{{ . }}){{ end }}
+{{ else }}
+## [{{ .ID }}](../end-device/{{ .File }}): {{ .Name }}
+{{ end }}
>> {{ .Description }}
-![{{ .ID }}](images/{{ .ID }}.yml.svg)
+![{{ .ID }}](images/end-device/{{ .ID }}.svg)
+
+
+{{ end }}
+
+{{- range .GatewayDescriptions }}
+
+{{ if .BaseID }}
+## `{{ .ID }}`: {{ .Name }}
+Based on [{{ .BaseID }}](##{{ .BaseID }}) and modified by {{ range .Modifiers }}[{{ . }}](../gateway/modifiers/{{ . }}){{ end }}
+{{ else }}
+## [{{ .ID }}](../gateway/{{ .File }}): {{ .Name }}
+{{ end }}
+
+>> {{ .Description }}
+
+![{{ .ID }}](images/gateway/{{ .ID }}.svg)
{{ end }}
diff --git a/internal/docs/gateway.go b/internal/docs/gateway.go
new file mode 100644
index 0000000..0a3a4bf
--- /dev/null
+++ b/internal/docs/gateway.go
@@ -0,0 +1,180 @@
+package docs
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "sort"
+
+ "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model"
+ "github.com/wcharczuk/go-chart/v2"
+)
+
+func renderGateway(id string, plan model.FrequencyPlanGateway) error {
+ frequencies := make(map[float64]string)
+
+ graph := chart.Chart{
+ Title: id,
+ Width: 1920,
+ Height: 1080,
+ DPI: 150,
+ }
+
+ annotations := chart.AnnotationSeries{}
+
+ for _, ch := range plan.Channels {
+ freq := float64(*ch.UplinkFrequency)
+ start, end := freq-62500, freq+62500
+ frequencies[freq] = formatFrequency(freq)
+ color := chart.GetDefaultColor(int(*ch.Radio))
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(1), float64(1)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: 1,
+ Label: formatFrequency(freq),
+ })
+
+ freq = float64(*ch.DownlinkFrequency)
+ start, end = freq-62500, freq+62500
+ frequencies[freq] = formatFrequency(freq)
+ color = chart.GetDefaultColor(3)
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(-1), float64(-1)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: -1,
+ Label: formatFrequency(freq),
+ })
+ }
+
+ if ch := plan.LoRaStandardChannel; ch != nil {
+ freq := float64(*ch.Frequency)
+ start, end := freq-125000, freq+125000
+ frequencies[freq] = formatFrequency(freq)
+ color := chart.GetDefaultColor(int(*ch.Radio))
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(2), float64(2)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: 2,
+ Label: formatFrequency(freq) + " (Std)",
+ })
+ }
+
+ if ch := plan.FSKChannel; ch != nil {
+ freq := float64(*ch.Frequency)
+ start, end := freq-62500, freq+62500
+ frequencies[freq] = formatFrequency(freq)
+ color := chart.GetDefaultColor(int(*ch.Radio))
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ FillColor: color,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(2), float64(2)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: 2,
+ Label: formatFrequency(freq) + " (FSK)",
+ })
+ }
+
+ for i, radio := range plan.Radios {
+ freq := float64(*radio.Frequency)
+ start, end := freq-462500, freq+462500
+ frequencies[start] = formatFrequency(start)
+ frequencies[freq] = formatFrequency(freq)
+ frequencies[end] = formatFrequency(end)
+ color := chart.GetDefaultColor(i)
+ color.A = 128
+ graph.Series = append(graph.Series, chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: color,
+ StrokeWidth: 10,
+ },
+ XValues: []float64{start, end},
+ YValues: []float64{float64(0), float64(0)},
+ })
+ annotations.Annotations = append(annotations.Annotations, chart.Value2{
+ XValue: freq,
+ YValue: 0,
+ Label: fmt.Sprintf("Radio %d: %s", i, formatFrequency(freq)),
+ })
+ }
+
+ var frequencySlice []float64
+ for frequency := range frequencies {
+ frequencySlice = append(frequencySlice, frequency)
+ }
+ sort.Float64s(frequencySlice)
+
+ graph.XAxis = chart.XAxis{
+ Range: &chart.ContinuousRange{
+ Min: frequencySlice[0],
+ Max: frequencySlice[len(frequencySlice)-1],
+ },
+ TickStyle: chart.Style{
+ TextRotationDegrees: 45.0,
+ },
+ }
+
+ for _, freq := range frequencySlice {
+ graph.XAxis.Ticks = append(graph.XAxis.Ticks, chart.Tick{
+ Value: freq,
+ Label: frequencies[freq],
+ })
+ }
+
+ graph.YAxis = chart.YAxis{
+ Range: &chart.ContinuousRange{
+ Min: -2,
+ Max: 3,
+ },
+ Ticks: []chart.Tick{
+ {Value: -2},
+ {Value: -1, Label: "Downlink"},
+ {Value: 0, Label: "Radio"},
+ {Value: 1, Label: "Uplink"},
+ {Value: 2, Label: "Std/FSK"},
+ {Value: 3},
+ },
+ }
+
+ graph.Series = append(graph.Series, annotations)
+
+ var buf bytes.Buffer
+
+ if err := graph.Render(chart.SVG, &buf); err != nil {
+ return err
+ }
+ if err := os.WriteFile("./docs/images/gateway/"+id+".svg", buf.Bytes(), 0o644); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/internal/model/end-device-frequency-plan.go b/internal/model/end-device-frequency-plan.go
index 6e00336..3f98366 100644
--- a/internal/model/end-device-frequency-plan.go
+++ b/internal/model/end-device-frequency-plan.go
@@ -93,42 +93,18 @@ func (f FrequencyPlanEndDevice) Validate() error {
func (f FrequencyPlanEndDevice) Modify(modifier FrequencyPlanEndDeviceModifier) FrequencyPlanEndDevice {
modified := f
- if modifier.SubBands != nil {
- modified.SubBands = *modifier.SubBands
- }
- if modifier.Channels != nil {
- modified.Channels = *modifier.Channels
- }
- if modifier.LoRaStandardChannel != nil {
- *modified.LoRaStandardChannel = *modifier.LoRaStandardChannel
- }
- if modifier.FSKChannel != nil {
- *modified.FSKChannel = *modifier.FSKChannel
- }
- if modifier.TimeOffAir != nil {
- *modified.TimeOffAir = *modifier.TimeOffAir
- }
- if modifier.DwellTime != nil {
- *modified.DwellTime = *modifier.DwellTime
- }
- if modifier.ListenBeforeTalk != nil {
- *modified.ListenBeforeTalk = *modifier.ListenBeforeTalk
- }
- if modifier.PingSlot != nil {
- *modified.PingSlot = *modifier.PingSlot
- }
- if modifier.PingSlotDefaultDataRate != nil {
- *modified.PingSlotDefaultDataRate = *modifier.PingSlotDefaultDataRate
- }
- if modifier.RX2Channel != nil {
- *modified.RX2Channel = *modifier.RX2Channel
- }
- if modifier.RX2DefaultDataRate != nil {
- *modified.RX2DefaultDataRate = *modifier.RX2DefaultDataRate
- }
- if modifier.MaxEIRP != nil {
- *modified.MaxEIRP = *modifier.MaxEIRP
- }
+ set(modifier.SubBands, modified.SubBands)
+ set(modifier.Channels, modified.Channels)
+ setPointer(modifier.LoRaStandardChannel, modified.LoRaStandardChannel)
+ setPointer(modifier.FSKChannel, modified.FSKChannel)
+ setPointer(modifier.TimeOffAir, modified.TimeOffAir)
+ setPointer(modifier.DwellTime, modified.DwellTime)
+ setPointer(modifier.ListenBeforeTalk, modified.ListenBeforeTalk)
+ setPointer(modifier.PingSlot, modified.PingSlot)
+ setPointer(modifier.PingSlotDefaultDataRate, modified.PingSlotDefaultDataRate)
+ setPointer(modifier.RX2Channel, modified.RX2Channel)
+ setPointer(modifier.RX2DefaultDataRate, modified.RX2DefaultDataRate)
+ setPointer(modifier.MaxEIRP, modified.MaxEIRP)
return modified
}
diff --git a/internal/model/frequency-plan-description.go b/internal/model/frequency-plan-description.go
index fc43bea..805e041 100644
--- a/internal/model/frequency-plan-description.go
+++ b/internal/model/frequency-plan-description.go
@@ -111,3 +111,7 @@ func (f FrequencyPlanDescription) Validate(source FrequencyPlanType) error {
return nil
}
+
+func (f FrequencyPlanDescription) HasModifiers() bool {
+ return f.BaseID != nil && f.Modifiers != nil
+}
diff --git a/internal/model/gateway-frequency-plan.go b/internal/model/gateway-frequency-plan.go
index d0f2fea..a5ff6cf 100644
--- a/internal/model/gateway-frequency-plan.go
+++ b/internal/model/gateway-frequency-plan.go
@@ -70,33 +70,15 @@ func (f FrequencyPlanGateway) Validate() error {
func (f FrequencyPlanGateway) Modify(modifier FrequencyPlanGatewayModifier) FrequencyPlanGateway {
modified := f
- if modifier.SubBands != nil {
- modified.SubBands = *modifier.SubBands
- }
- if modifier.Channels != nil {
- modified.Channels = *modifier.Channels
- }
- if modifier.LoRaStandardChannel != nil {
- *modified.LoRaStandardChannel = *modifier.LoRaStandardChannel
- }
- if modifier.FSKChannel != nil {
- *modified.FSKChannel = *modifier.FSKChannel
- }
- if modifier.TimeOffAir != nil {
- *modified.TimeOffAir = *modifier.TimeOffAir
- }
- if modifier.DwellTime != nil {
- *modified.DwellTime = *modifier.DwellTime
- }
- if modifier.Radios != nil {
- modified.Radios = *modifier.Radios
- }
- if modifier.ClockSource != nil {
- modified.ClockSource = *modifier.ClockSource
- }
- if modifier.MaxEIRP != nil {
- *modified.MaxEIRP = *modifier.MaxEIRP
- }
+ set(modifier.SubBands, modified.SubBands)
+ set(modifier.Channels, modified.Channels)
+ set(modifier.Radios, modified.Radios)
+ set(modifier.ClockSource, modified.ClockSource)
+ setPointer(modifier.LoRaStandardChannel, modified.LoRaStandardChannel)
+ setPointer(modifier.FSKChannel, modified.FSKChannel)
+ setPointer(modifier.TimeOffAir, modified.TimeOffAir)
+ setPointer(modifier.DwellTime, modified.DwellTime)
+ setPointer(modifier.MaxEIRP, modified.MaxEIRP)
return modified
}
diff --git a/internal/model/model.go b/internal/model/model.go
index 49eb089..0546380 100644
--- a/internal/model/model.go
+++ b/internal/model/model.go
@@ -239,3 +239,18 @@ func validateFrequencyRange(frequency int, min, max int) error {
}
return nil
}
+
+func set[T any](modifier *T, base T) {
+ if modifier != nil {
+ base = *modifier
+ }
+}
+
+func setPointer[T any](modifier *T, base *T) {
+ if modifier != nil {
+ if base == nil {
+ base = new(T)
+ }
+ *base = *modifier
+ }
+}