Skip to content

Commit 489d4c8

Browse files
author
Yorick Smilda
committed
Update docs to support modifiers
1 parent 623316b commit 489d4c8

13 files changed

Lines changed: 1344 additions & 294 deletions

README.md

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,30 @@ max-eirp: 29.15 # Maximum EIRP (optional; used when sub-bands do
102102
103103
An index of frequency plans is in `frequency-plans.yml`. This can take two forms depending on if it is a root frequency plan definition, or if it is a plan definition that modifies an existing plan.
104104

105-
Root definition
105+
Base plan definition
106106
```yml
107-
- id: EU_863_870_TTN # ID of the frequency plan
108-
band-id: EU_863_870 # ID of the LoRaWAN band (needs to match band-id in the definition)
109-
name: Region 863-870 MHz # Name of the frequency plan, ending with frequency ranges
110-
description: Default frequency plan for Europe # Description of the frequency plan
111-
base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868, 915 or 2450)
112-
country-codes: [] # List of 2-digit ISO country codes for countries where this plan can be used
113-
device-file: end-device/EU_863_870.yml # File of the freqeuency plan definition
114-
gateway-file: gateway/EU_863_870.yml
115-
phy-versions: []
116-
endorsed: true
107+
end-device-descriptions:
108+
- id: EU_863_870 # ID of the frequency plan
109+
band-id: EU_863_870 # ID of the LoRaWAN band (needs to match band-id in the definition)
110+
name: Region 863-870 MHz # Name of the frequency plan, ending with frequency ranges
111+
description: Default frequency plan for Europe # Description of the frequency plan
112+
base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868, 915 or 2450)
113+
country-codes: [] # List of 2-digit ISO country codes for countries where this plan can be used
114+
file: EU_863_870.yml # Filename of the plan residing in the `end-device` folder
115+
endorsed: true
116+
117+
gateway-descriptions:
118+
- id: EU_863_870 # ID of the frequency plan
119+
band-id: EU_863_870 # ID of the LoRaWAN band (needs to match band-id in the definition)
120+
name: Region 863-870 MHz # Name of the frequency plan, ending with frequency ranges
121+
description: Default frequency plan for Europe # Description of the frequency plan
122+
base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868, 915 or 2450)
123+
country-codes: [] # List of 2-digit ISO country codes for countries where this plan can be used
124+
file: EU_863_870.yml # Filename of the plan residing in the `gateway` folder
125+
endorsed: true
117126
```
118127
119-
Modifying definition
128+
Inherited definition
120129
```yml
121130
- id: EU_863_870_TTN # ID of the frequency plan
122131
band-id: EU_863_870 # ID of the LoRaWAN band (needs to match band-id in the definition)
@@ -125,9 +134,7 @@ Modifying definition
125134
description: Default frequency plan for Europe # Description of the frequency plan
126135
base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868, 915 or 2450)
127136
country-codes: [] # List of 2-digit ISO country codes for countries where this plan can be used
128-
device-modifiers: []
129-
gateway-modifiers: []
130-
phy-versions: []
137+
modifiers: [] # Filename of the modifier residing in the respective `end-device/modifiers` or `gateway/modifiers` folder.
131138
endorsed: true
132139
```
133140

docs/frequency-plans.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# LoRaWAN Frequency Plans for The Things Stack
2+
3+
# End device frequency plans
4+
5+
## [`EU_863_870`](../end-device/EU_863_870.yml): Europe 863-870 MHz
6+
7+
>> Default frequency plan for Europe
8+
9+
![EU_863_870](images/end-device/EU_863_870.svg)
10+
11+
## `EU_863_870_TTN`: Europe 863-870 MHz
12+
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)
13+
14+
15+
>> TTN Community Network frequency plan for Europe, using SF9 for RX2
16+
17+
![EU_863_870_TTN](images/end-device/EU_863_870_TTN.svg)
18+
19+
# Gateway frequency plans
20+
21+
22+
## [`EU_863_870`](../gateway/EU_863_870.yml): Europe 863-870 MHz
23+
24+
>> Default frequency plan for Europe
25+
26+
![EU_863_870](images/gateway/EU_863_870.svg)

docs/images/end-device/EU_863_870.svg

Lines changed: 271 additions & 0 deletions
Loading

docs/images/end-device/EU_863_870_TTN.svg

Lines changed: 271 additions & 0 deletions
Loading

docs/images/gateway/EU_863_870.svg

Lines changed: 304 additions & 0 deletions
Loading

internal/docs/docs.go

Lines changed: 55 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@ package docs
33
import (
44
"bytes"
55
"embed"
6+
"errors"
67
"fmt"
7-
"log"
88
"os"
9-
"sort"
109
"strings"
1110
"text/template"
1211

13-
"github.com/wcharczuk/go-chart/v2"
14-
"go.thethings.network/lorawan-stack/v3/pkg/frequencyplans"
15-
1612
"github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model"
1713
)
1814

@@ -23,35 +19,22 @@ var tmpl = template.Must(template.ParseFS(fsys, "*.tmpl"))
2319

2420
// Generate generates the documentation for the frequency-plans.
2521
func Generate(sourceFile, destinationFolder string) error {
26-
plans := model.FrequencyPlanDescriptions{}
27-
output, err := plans.Parse(sourceFile)
22+
output, err := model.FrequencyPlanDescriptions{}.Parse(sourceFile)
2823
if err != nil {
2924
return err
3025
}
26+
descriptions := output.(model.FrequencyPlanDescriptions)
3127

32-
for _, plan := range output.(model.FrequencyPlanDescriptions).EndDeviceDescriptions {
33-
// TODO: Support extensions
34-
if plan.BaseID != nil {
35-
log.Printf("Skipping %s: extending a base plan not supported yet", plan.ID)
36-
continue
37-
}
38-
if err := renderEndDevice(plan.ID, *plan.File); err != nil {
39-
return err
40-
}
28+
if err := renderPlans("gateway", descriptions.GatewayDescriptions, model.FrequencyPlanGateway{}); err != nil {
29+
return err
4130
}
42-
for _, plan := range output.(model.FrequencyPlanDescriptions).GatewayDescriptions {
43-
// TODO: Support extensions
44-
if plan.BaseID != nil {
45-
log.Printf("Skipping %s: extending a base plan not supported yet", plan.ID)
46-
continue
47-
}
48-
if err := renderEndDevice(plan.ID, *plan.File); err != nil {
49-
return err
50-
}
31+
32+
if err := renderPlans("end-device", descriptions.EndDeviceDescriptions, model.FrequencyPlanEndDevice{}); err != nil {
33+
return err
5134
}
5235

5336
var buf bytes.Buffer
54-
if err := tmpl.ExecuteTemplate(&buf, "frequency-plans.md.tmpl", plans); err != nil {
37+
if err := tmpl.ExecuteTemplate(&buf, "frequency-plans.md.tmpl", output); err != nil {
5538
return err
5639
}
5740
if err := os.WriteFile(destinationFolder+"/frequency-plans.md", buf.Bytes(), 0o644); err != nil {
@@ -64,195 +47,55 @@ func formatFrequency(f float64) string {
6447
return strings.TrimRight(fmt.Sprintf("%.3f", f/1_000_000), "0.")
6548
}
6649

67-
func renderEndDevice(id, file string) error {
68-
plan := model.FrequencyPlanEndDevice{}
69-
_, err := plan.Parse("./end-device/" + file)
70-
if err != nil {
71-
return err
72-
}
73-
74-
// return render(id, "./docs/images/end-device/", plan)
75-
return nil
76-
}
77-
78-
func renderGateway(id, file string) error {
79-
plan := model.FrequencyPlanGateway{}
80-
_, err := plan.Parse("./gateway/" + file)
81-
if err != nil {
82-
return err
50+
func renderPlans(folder string, descriptions []model.FrequencyPlanDescription, definition model.Definition) error {
51+
for _, plan := range descriptions {
52+
fileName := ""
53+
if plan.HasModifiers() {
54+
for _, description := range descriptions {
55+
if description.ID == *plan.BaseID {
56+
fileName = *description.File
57+
break
58+
}
59+
}
60+
} else {
61+
fileName = *plan.File
62+
}
63+
basePlan, err := definition.Parse(folder + "/" + fileName)
64+
if err != nil {
65+
return err
66+
}
67+
if plan.HasModifiers() {
68+
for _, modifierName := range *plan.Modifiers {
69+
switch definition.(type) {
70+
case model.FrequencyPlanEndDevice:
71+
modifier, err := model.FrequencyPlanEndDeviceModifier{}.Parse(folder + "/modifiers/" + modifierName)
72+
if err != nil {
73+
return err
74+
}
75+
basePlan = basePlan.(model.FrequencyPlanEndDevice).Modify(modifier.(model.FrequencyPlanEndDeviceModifier))
76+
case model.FrequencyPlanGateway:
77+
modifier, err := model.FrequencyPlanGatewayModifier{}.Parse(folder + "/modifiers/" + modifierName)
78+
if err != nil {
79+
return err
80+
}
81+
basePlan = basePlan.(model.FrequencyPlanGateway).Modify(modifier.(model.FrequencyPlanGatewayModifier))
82+
}
83+
}
84+
}
85+
if err := render(plan.ID, basePlan); err != nil {
86+
return err
87+
}
8388
}
84-
85-
// return render(id, "./docs/images/gateway/", plan)
8689
return nil
8790
}
8891

89-
func render(id string, folder string, plan frequencyplans.FrequencyPlan) error {
90-
frequencies := make(map[float64]string)
91-
92-
graph := chart.Chart{
93-
Title: id,
94-
Width: 1920,
95-
Height: 1080,
96-
DPI: 150,
97-
}
98-
99-
annotations := chart.AnnotationSeries{}
100-
101-
for _, ch := range plan.UplinkChannels {
102-
freq := float64(ch.Frequency)
103-
start, end := freq-62500, freq+62500
104-
frequencies[freq] = formatFrequency(freq)
105-
color := chart.GetDefaultColor(int(ch.Radio))
106-
color.A = 128
107-
graph.Series = append(graph.Series, chart.ContinuousSeries{
108-
Style: chart.Style{
109-
StrokeColor: color,
110-
FillColor: color,
111-
},
112-
XValues: []float64{start, end},
113-
YValues: []float64{float64(1), float64(1)},
114-
})
115-
annotations.Annotations = append(annotations.Annotations, chart.Value2{
116-
XValue: freq,
117-
YValue: 1,
118-
Label: formatFrequency(freq),
119-
})
120-
}
121-
122-
if ch := plan.LoRaStandardChannel; ch != nil {
123-
freq := float64(ch.Frequency)
124-
start, end := freq-125000, freq+125000
125-
frequencies[freq] = formatFrequency(freq)
126-
color := chart.GetDefaultColor(int(ch.Radio))
127-
color.A = 128
128-
graph.Series = append(graph.Series, chart.ContinuousSeries{
129-
Style: chart.Style{
130-
StrokeColor: color,
131-
FillColor: color,
132-
},
133-
XValues: []float64{start, end},
134-
YValues: []float64{float64(2), float64(2)},
135-
})
136-
annotations.Annotations = append(annotations.Annotations, chart.Value2{
137-
XValue: freq,
138-
YValue: 2,
139-
Label: formatFrequency(freq) + " (Std)",
140-
})
141-
}
142-
143-
if ch := plan.FSKChannel; ch != nil {
144-
freq := float64(ch.Frequency)
145-
start, end := freq-62500, freq+62500
146-
frequencies[freq] = formatFrequency(freq)
147-
color := chart.GetDefaultColor(int(ch.Radio))
148-
color.A = 128
149-
graph.Series = append(graph.Series, chart.ContinuousSeries{
150-
Style: chart.Style{
151-
StrokeColor: color,
152-
FillColor: color,
153-
},
154-
XValues: []float64{start, end},
155-
YValues: []float64{float64(2), float64(2)},
156-
})
157-
annotations.Annotations = append(annotations.Annotations, chart.Value2{
158-
XValue: freq,
159-
YValue: 2,
160-
Label: formatFrequency(freq) + " (FSK)",
161-
})
162-
}
163-
164-
for _, ch := range plan.DownlinkChannels {
165-
freq := float64(ch.Frequency)
166-
start, end := freq-62500, freq+62500
167-
frequencies[freq] = formatFrequency(freq)
168-
color := chart.GetDefaultColor(3)
169-
color.A = 128
170-
graph.Series = append(graph.Series, chart.ContinuousSeries{
171-
Style: chart.Style{
172-
StrokeColor: color,
173-
FillColor: color,
174-
},
175-
XValues: []float64{start, end},
176-
YValues: []float64{float64(-1), float64(-1)},
177-
})
178-
annotations.Annotations = append(annotations.Annotations, chart.Value2{
179-
XValue: freq,
180-
YValue: -1,
181-
Label: formatFrequency(freq),
182-
})
183-
}
184-
185-
for i, radio := range plan.Radios {
186-
freq := float64(radio.Frequency)
187-
start, end := freq-462500, freq+462500
188-
frequencies[start] = formatFrequency(start)
189-
frequencies[freq] = formatFrequency(freq)
190-
frequencies[end] = formatFrequency(end)
191-
color := chart.GetDefaultColor(i)
192-
color.A = 128
193-
graph.Series = append(graph.Series, chart.ContinuousSeries{
194-
Style: chart.Style{
195-
StrokeColor: color,
196-
StrokeWidth: 10,
197-
},
198-
XValues: []float64{start, end},
199-
YValues: []float64{float64(0), float64(0)},
200-
})
201-
annotations.Annotations = append(annotations.Annotations, chart.Value2{
202-
XValue: freq,
203-
YValue: 0,
204-
Label: fmt.Sprintf("Radio %d: %s", i, formatFrequency(freq)),
205-
})
206-
}
207-
208-
var frequencySlice []float64
209-
for frequency := range frequencies {
210-
frequencySlice = append(frequencySlice, frequency)
211-
}
212-
sort.Float64s(frequencySlice)
213-
214-
graph.XAxis = chart.XAxis{
215-
Range: &chart.ContinuousRange{
216-
Min: frequencySlice[0],
217-
Max: frequencySlice[len(frequencySlice)-1],
218-
},
219-
TickStyle: chart.Style{
220-
TextRotationDegrees: 45.0,
221-
},
92+
func render(id string, definition model.Definition) error {
93+
switch mod := definition.(type) {
94+
case model.FrequencyPlanGateway:
95+
return renderGateway(id, mod)
96+
case model.FrequencyPlanEndDevice:
97+
return renderEndDevice(id, mod)
98+
default:
99+
return errors.New("unsupported type")
222100
}
223-
224-
for _, freq := range frequencySlice {
225-
graph.XAxis.Ticks = append(graph.XAxis.Ticks, chart.Tick{
226-
Value: freq,
227-
Label: frequencies[freq],
228-
})
229-
}
230-
231-
graph.YAxis = chart.YAxis{
232-
Range: &chart.ContinuousRange{
233-
Min: -2,
234-
Max: 3,
235-
},
236-
Ticks: []chart.Tick{
237-
{Value: -2},
238-
{Value: -1, Label: "Downlink"},
239-
{Value: 0, Label: "Radio"},
240-
{Value: 1, Label: "Uplink"},
241-
{Value: 2, Label: "Std/FSK"},
242-
{Value: 3},
243-
},
244-
}
245-
246-
graph.Series = append(graph.Series, annotations)
247-
248-
var buf bytes.Buffer
249-
250-
if err := graph.Render(chart.SVG, &buf); err != nil {
251-
return err
252-
}
253-
if err := os.WriteFile(folder+id+".svg", buf.Bytes(), 0o644); err != nil {
254-
return err
255-
}
256-
257-
return nil
258101
}

0 commit comments

Comments
 (0)