mirror of
https://codeberg.org/portospaceteam/ground-dashboard.git
synced 2024-11-25 00:25:57 +00:00
228 lines
4.8 KiB
Go
228 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
"time"
|
|
|
|
ui "github.com/gizak/termui/v3"
|
|
"github.com/gizak/termui/v3/widgets"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/johnjones4/model-rocket-telemetry/dashboard/core"
|
|
)
|
|
|
|
const SecondsWindow = 20
|
|
|
|
//go:embed static/**
|
|
var static embed.FS
|
|
|
|
type staticFS struct {
|
|
content embed.FS
|
|
}
|
|
|
|
func (c staticFS) Open(name string) (fs.File, error) {
|
|
return c.content.Open(path.Join("static", name))
|
|
}
|
|
|
|
func StartTextLogger(p DataProvider, ds core.FlightData, logger LoggerControl) error {
|
|
if err := ui.Init(); err != nil {
|
|
return err
|
|
}
|
|
defer ui.Close()
|
|
|
|
headers := []string{
|
|
"Time",
|
|
"Phase",
|
|
"Altitude",
|
|
"Velocity",
|
|
"Acceleration",
|
|
"Temperature",
|
|
"Latitude",
|
|
"longitude",
|
|
"GPS Quality",
|
|
"GPS Sats",
|
|
"Qual",
|
|
"RSSI",
|
|
}
|
|
|
|
grid := ui.NewGrid()
|
|
termWidth, termHeight := ui.TerminalDimensions()
|
|
grid.SetRect(0, 0, termWidth, termHeight)
|
|
|
|
table := widgets.NewTable()
|
|
table.Title = "Data Stream"
|
|
table.Rows = [][]string{headers}
|
|
|
|
errorsui := widgets.NewList()
|
|
errorsui.Title = "Errors"
|
|
errorsui.Rows = []string{}
|
|
errorsui.WrapText = false
|
|
errorsList := make([]error, 0)
|
|
|
|
grid.Set(
|
|
ui.NewRow(0.8,
|
|
ui.NewCol(1.0, table),
|
|
),
|
|
ui.NewRow(0.2,
|
|
ui.NewCol(1.0, errorsui),
|
|
),
|
|
)
|
|
|
|
uiEvents := ui.PollEvents()
|
|
streamChannel := p.Stream()
|
|
|
|
renderTable := func() {
|
|
data := ds.AllSegments()
|
|
|
|
if len(data) == 0 {
|
|
ui.Render(grid)
|
|
return
|
|
}
|
|
nRows := (table.Inner.Dy() + 1) / 2
|
|
if nRows <= 0 {
|
|
nRows = 10
|
|
}
|
|
if nRows > len(data)+1 {
|
|
nRows = len(data) + 1
|
|
}
|
|
rows := make([][]string, nRows)
|
|
rows[0] = headers
|
|
for i := 0; i < nRows-1; i++ {
|
|
j := len(data) - nRows + 1 + i
|
|
seg := data[j]
|
|
rows[i+1] = []string{
|
|
fmt.Sprintf("%0.2f", seg.Raw.FlightTime),
|
|
fmt.Sprintf("%0.2f", seg.Raw.FlightPhase),
|
|
fmt.Sprintf("%0.2f", seg.Raw.Altitude),
|
|
fmt.Sprintf("%0.2f", seg.Raw.Velocity),
|
|
fmt.Sprintf("%0.2f", seg.Raw.Acceleration),
|
|
fmt.Sprintf("%0.2f", seg.Raw.Temperature),
|
|
fmt.Sprintf("%0.2f", seg.Raw.Coordinate.Lat),
|
|
fmt.Sprintf("%0.2f", seg.Raw.Coordinate.Lon),
|
|
fmt.Sprintf("%0.2f", seg.Raw.GPSInfo.Sats),
|
|
fmt.Sprintf("%0.2f", seg.Raw.GPSInfo.Quality),
|
|
fmt.Sprintf("%d", seg.Raw.Rssi),
|
|
}
|
|
}
|
|
table.Rows = rows
|
|
ui.Render(grid)
|
|
}
|
|
|
|
renderErrors := func() {
|
|
if len(errorsList) == 0 {
|
|
ui.Render(grid)
|
|
return
|
|
}
|
|
|
|
nRows := errorsui.Inner.Dy()
|
|
if nRows <= 0 {
|
|
nRows = 10
|
|
}
|
|
if nRows > len(errorsList) {
|
|
nRows = len(errorsList)
|
|
}
|
|
rows := make([]string, nRows)
|
|
for i := 0; i < nRows; i++ {
|
|
j := len(errorsList) - nRows + i
|
|
rows[i] = fmt.Sprint(errorsList[j])
|
|
}
|
|
errorsui.Rows = rows
|
|
|
|
ui.Render(grid)
|
|
}
|
|
|
|
renderTable()
|
|
|
|
for {
|
|
select {
|
|
case e := <-uiEvents:
|
|
switch e.ID {
|
|
case "q", "<C-c>":
|
|
return nil
|
|
}
|
|
case bytes := <-streamChannel:
|
|
latestSegments, basePressure, origin, err := bytesToDataSegment(ds, bytes)
|
|
if err != nil {
|
|
errorsList = append(errorsList, err)
|
|
renderErrors()
|
|
} else {
|
|
ds.AppendData(latestSegments)
|
|
ds.SetBasePressure(basePressure)
|
|
ds.SetOrigin(origin)
|
|
renderTable()
|
|
for _, seg := range latestSegments {
|
|
logger.Log(seg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func StartWeb(p DataProvider, ds core.FlightData, logger LoggerControl) error {
|
|
var mutex sync.Mutex
|
|
var upgrader = websocket.Upgrader{}
|
|
http.HandleFunc("/api/data", func(w http.ResponseWriter, req *http.Request) {
|
|
c, err := upgrader.Upgrade(w, req, nil)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
mutex.Lock()
|
|
data := ds.AllSegments()
|
|
lastLength := len(data)
|
|
err = c.WriteJSON(data)
|
|
mutex.Unlock()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
for {
|
|
mutex.Lock()
|
|
data = ds.AllSegments()
|
|
if len(data) > lastLength {
|
|
err = c.WriteJSON(data[lastLength:])
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
mutex.Unlock()
|
|
return
|
|
}
|
|
lastLength = len(data)
|
|
}
|
|
mutex.Unlock()
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
})
|
|
if os.Getenv("DEV_MODE") == "" {
|
|
http.Handle("/", http.FileServer(http.FS(staticFS{static})))
|
|
} else {
|
|
http.Handle("/", http.FileServer(http.Dir("dashboard/static")))
|
|
}
|
|
go func() {
|
|
streamChannel := p.Stream()
|
|
for {
|
|
bytes := <-streamChannel
|
|
latestSegments, basePressure, origin, err := bytesToDataSegment(ds, bytes)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
mutex.Lock()
|
|
ds.AppendData(latestSegments)
|
|
ds.SetBasePressure(basePressure)
|
|
ds.SetOrigin(origin)
|
|
mutex.Unlock()
|
|
for _, seg := range latestSegments {
|
|
logger.Log(seg)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return http.ListenAndServe(":8080", nil)
|
|
}
|