Skip to content

Commit

Permalink
Update docs to support modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
Yorick Smilda committed Jan 30, 2023
1 parent 623316b commit 158d637
Show file tree
Hide file tree
Showing 12 changed files with 1,334 additions and 277 deletions.
34 changes: 34 additions & 0 deletions docs/frequency-plans.md
Original file line number Diff line number Diff line change
@@ -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)


271 changes: 271 additions & 0 deletions docs/images/end-device/EU_863_870.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
271 changes: 271 additions & 0 deletions docs/images/end-device/EU_863_870_TTN.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
304 changes: 304 additions & 0 deletions docs/images/gateway/EU_863_870.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
267 changes: 55 additions & 212 deletions internal/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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 {
Expand All @@ -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
}
Loading

0 comments on commit 158d637

Please sign in to comment.