1package webui
2
3import (
4 "ewdetect/comms"
5 "ewdetect/config"
6 "ewdetect/stations"
7 "ewdetect/utils"
8 "fmt"
9 "slices"
10 "time"
11
12 sm "github.com/flopp/go-staticmaps"
13 "github.com/fogleman/gg"
14 "github.com/gofiber/fiber/v3"
15 "github.com/golang/geo/s2"
16 "github.com/rs/zerolog/log"
17
18 "github.com/go-echarts/go-echarts/v2/charts"
19 "github.com/go-echarts/go-echarts/v2/opts"
20 "github.com/go-echarts/go-echarts/v2/types"
21)
22
23// generate random data for line chart
24func generateTimeSeries(connection, key string) []string {
25 head := (*comms.Connections[connection].Heads)[key]
26 headLoops := (*comms.Connections[connection].HeadLoops)[key]
27 items := make([]string, 0)
28 sampleRate := float64((*stations.Stations[connection])[key].SampleRate)
29 var startTime time.Time
30 for i := range config.BufferSize {
31 startTime = (*comms.Connections[connection].StartTime)
32 startTime = startTime.Add(time.Duration(1e9 * float64(i) / sampleRate))
33 startTime = startTime.Add(time.Duration(1e9 * float64((headLoops-1)*config.BufferSize) / sampleRate))
34 if i <= head {
35 startTime = startTime.Add(time.Duration(1e9 * float64(config.BufferSize) / sampleRate))
36 }
37 items = append(items, startTime.Format("2006-01-02T15:04:05.000"))
38 }
39 return items
40}
41
42func generateLineItems(connection, key string) []opts.LineData {
43 items := make([]opts.LineData, 0)
44 //fmt.Println(comms.Buffers["MHGZ"])
45 for _, b := range *(*comms.Connections[connection].Buffers)[key] {
46 curr_b := b
47
48 if _, ok := stations.Stations[connection]; ok {
49 if (*stations.Stations[connection])[key].Calibrated {
50 curr_b = curr_b - (*stations.Stations[connection])[key].DC
51 curr_b = curr_b * 1024 / (*stations.Stations[connection])[key].NoiseFloor
52 }
53 }
54 items = append(items, opts.LineData{Value: b})
55 }
56 return items
57}
58
59func chart(c fiber.Ctx) error {
60 log.Debug().Msg("Generating chart view")
61 c.Type(".html")
62 count := 0
63 for connection := range comms.Connections {
64 for _ = range *comms.Connections[connection].Buffers {
65 count++
66 }
67 }
68 if config.Debug {
69 log.Debug().Msg("Generating debug station map")
70 ctx := sm.NewContext()
71 ctx.SetSize(config.DebugResolution, config.DebugResolution)
72 for connection := range comms.Connections {
73 if _, ok := stations.Stations[connection]; ok {
74 for key := range *comms.Connections[connection].Buffers {
75 ctx.AddObject(
76 sm.NewMarker(
77 s2.LatLngFromDegrees((*stations.Stations[connection])[key].Lat, (*stations.Stations[connection])[key].Lon),
78 utils.ColorFromHash(connection),
79 16.0,
80 ),
81 )
82 }
83 } else {
84 log.Warn().Str("connection", connection).Msg("Missing station metadata")
85 }
86 }
87 ctx.OverrideAttribution(fmt.Sprintf("%s - EWDetect - Louis \"OnTake\" Dalibard - 2025.", ctx.Attribution()))
88
89 img, err := ctx.Render()
90 if err != nil {
91 log.Fatal().Err(err).Msg("Failed to render debug map")
92 }
93
94 if err := gg.SavePNG("debug/station-map/stations.png", img); err != nil {
95 log.Fatal().Err(err).Msg("Failed to save debug map")
96 }
97 }
98 c.WriteString(fmt.Sprintf("<h1> Tracking %d stations</h1>", count))
99 for connection := range comms.Connections {
100 for key := range *comms.Connections[connection].Buffers {
101 if slices.Contains(config.WebUITrackedStations, key) {
102 // create a new line instance
103 line := charts.NewLine()
104 // set some global options like Title/Legend/ToolTip or anything else
105 line.SetGlobalOptions(
106 charts.WithInitializationOpts(opts.Initialization{Theme: types.ThemeWesteros}),
107 charts.WithTitleOpts(opts.Title{
108 Title: fmt.Sprintf("%s (%s)", (*stations.Stations[connection])[key].Name, key),
109 Subtitle: fmt.Sprintf("%0.4f, %0.4f", (*stations.Stations[connection])[key].Lat, (*stations.Stations[connection])[key].Lon),
110 }))
111
112 // Put data into instance
113 line.SetXAxis(generateTimeSeries(connection, key))
114 line.AddSeries(key, generateLineItems(connection, key))
115 line.SetSeriesOptions(charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(false)}))
116 line.Render(c)
117 }
118 }
119 }
120 return nil
121}
122
123func Init() {
124 log.Info().Msg("Initializing WebUI")
125 // Initialize a new Fiber app
126 app := fiber.New()
127
128 // Define a route for the GET method on the root path '/'
129 app.Get("/", chart)
130
131 // Start the server on port 3000
132 log.Info().Msg("Starting WebUI server on port 3000")
133 if err := app.Listen(":3000"); err != nil {
134 log.Fatal().Err(err).Msg("Failed to start WebUI server")
135 }
136}