Skip to content

Commit

Permalink
TUI is almost complete
Browse files Browse the repository at this point in the history
  • Loading branch information
bluntelk committed Sep 29, 2023
1 parent 2a35d81 commit 9bf12b0
Show file tree
Hide file tree
Showing 11 changed files with 811 additions and 105 deletions.
237 changes: 196 additions & 41 deletions cmd/ingest_tap/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,49 @@ package main

import (
"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/bubbles/viewport"
"github.com/charmbracelet/lipgloss"
"github.com/rs/zerolog"
"plane.watch/lib/export"
"plane.watch/lib/tracker/beast"
"plane.watch/lib/tracker/mode_s"
"plane.watch/lib/tracker/sbs1"
"sort"
"strconv"
"strings"
"sync"
"time"
)

type (
planesSource int
model struct {

sourceInfo struct {
mu sync.Mutex
frameCount uint64
planes map[string]*export.PlaneLocation
frames map[string]uint64
icaos []string
}

model struct {
logger zerolog.Logger

startTime time.Time

tapper *PlaneWatchTapper

focusIcaoList []string

statsTable table.Model
selectedTable table.Model
planesTable table.Model
source planesSource

inputAvr map[string][]string
statsTable table.Model
selectedTable table.Model
planesTable table.Model
source planesSource
selectedIcao string
selectedCallSign string

fromIngest map[string][]export.Aircraft
fromRouter map[string][]export.Aircraft
fromWsBroker map[string][]export.Aircraft
logView viewport.Model
logViewReady bool

width, height int
tickCount uint64
Expand All @@ -34,42 +53,65 @@ type (
heading lipgloss.Style

logs *strings.Builder

incomingMutex sync.Mutex

feederSources map[string]int
incomingIcaoFrames map[uint32]int
numIncomingBeast uint64
numIncomingAvr uint64
numIncomingSbs1 uint64

afterIngest sourceInfo
afterEnrichment sourceInfo
afterRouterLow sourceInfo
afterRouterHigh sourceInfo
finalLow sourceInfo
finalHigh sourceInfo
}

timerTick time.Time
)

func initialModel(natsUrl, wsUrl string) (*model, error) {
func initialModel(natsURL, wsURL string) (*model, error) {
logs := &strings.Builder{}
logger := zerolog.New(logs).With().Timestamp().Logger()
logger := zerolog.New(zerolog.ConsoleWriter{Out: logs, TimeFormat: time.UnixDate}).With().Timestamp().Logger()

m := &model{
logger: logger.With().Str("app", "model").Logger(),
startTime: time.Now(),
tapper: NewPlaneWatchTapper(WithLogger(logger)),
tickDuration: time.Millisecond * 16,
focusIcaoList: make([]string, 0),
inputAvr: make(map[string][]string),
fromIngest: make(map[string][]export.Aircraft),
fromRouter: make(map[string][]export.Aircraft),
fromWsBroker: make(map[string][]export.Aircraft),
source: planesSourceWS,
source: planesSourceWSLow,
logs: logs,

feederSources: make(map[string]int),
incomingIcaoFrames: make(map[uint32]int),
}
if err := m.tapper.Connect(natsUrl, wsUrl); err != nil {
if err := m.tapper.Connect(natsURL, wsURL); err != nil {
return nil, err
}
m.buildTables()
m.configureStyles()

m.afterIngest.init()
m.afterEnrichment.init()
m.afterRouterLow.init()
m.afterRouterHigh.init()
m.finalLow.init()
m.finalHigh.init()
m.logger.Debug().Msg("Startup Init Complete")

return m, nil
}

func (m *model) configureStyles() {
m.heading = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FFFFFF")).
PaddingTop(0).
PaddingLeft(3).
PaddingBottom(1)
m.heading = func() lipgloss.Style {
b := lipgloss.RoundedBorder()
b.Right = "├"
return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
}()

s := table.DefaultStyles()
s.Header = s.Header.
Expand All @@ -89,44 +131,157 @@ func (m *model) configureStyles() {
func (m *model) buildTables() {
m.statsTable = table.New(
table.WithColumns([]table.Column{
{Title: "# Receivers", Width: 11},
{Title: "# Beast", Width: 10},
{Title: "# Frames", Width: 10},
{Title: "# Low", Width: 10},
{Title: "# High", Width: 10},
{Title: "# WS", Width: 10},
{Title: "Receivers", Width: 11},
{Title: "Planes", Width: 10},

{Title: "Beast", Width: 10},
{Title: "Avr", Width: 5},
{Title: "Sbs1", Width: 5},

{Title: "Frames", Width: 10},

{Title: "Enriched", Width: 10},

{Title: "Routed Low", Width: 10},
{Title: "Routed High", Width: 11},

{Title: "WS Low", Width: 10},
{Title: "WS High", Width: 10},
}),
table.WithRows([]table.Row{
{"0", "0", "0", "0", "0", "0"},
{"0", "0", "0", "0", "0", "0", "0", "0", "0", "0"},
}),
table.WithHeight(2),
table.WithFocused(false),
)
m.selectedTable = table.New(
table.WithColumns([]table.Column{
{Title: "# Receivers", Width: 11},
{Title: "# Beast", Width: 10},
{Title: "# Frames", Width: 10},
{Title: "# Low", Width: 10},
{Title: "# High", Width: 10},
{Title: "# WS", Width: 10},
}),
table.WithRows([]table.Row{
{"0", "0", "0", "0", "0", "0"},
{Title: "Source", Width: 20},
{Title: "# Updates", Width: 10},
{Title: "Squawk", Width: 9},
{Title: "Lat", Width: 10},
{Title: "Lon", Width: 10},
{Title: "Altitude", Width: 10},
{Title: "Vert Rate", Width: 10},
{Title: "Heading", Width: 10},
}),
table.WithHeight(2),
table.WithRows(m.defaultSelectedTableRows()),
table.WithHeight(8),
table.WithFocused(false),
)
m.planesTable = table.New(
table.WithColumns([]table.Column{
{Title: "ICAO", Width: 6},
{Title: "Receivers", Width: 9},
{Title: "CallSign", Width: 9},
{Title: "Squawk", Width: 9},
{Title: "Lat", Width: 10},
{Title: "Lon", Width: 10},
{Title: "Altitude", Width: 10},
{Title: "Vert Rate", Width: 10},
{Title: "Heading", Width: 10},
}),
table.WithHeight(10),
table.WithRows([]table.Row{}),
table.WithFocused(true),
)
}

func (m *model) defaultSelectedTableRows() []table.Row {
return []table.Row{
{planesSourceIncoming.String(), "", "", "", "", "", "", ""},
{planesSourceIngest.String(), "", "", "", "", "", "", ""},
{planesSourceEnriched.String(), "", "", "", "", "", "", ""},
{planesSourceRoutedLow.String(), "", "", "", "", "", "", ""},
{planesSourceRoutedHigh.String(), "", "", "", "", "", "", ""},
{planesSourceWSLow.String(), "", "", "", "", "", "", ""},
{planesSourceWSHigh.String(), "", "", "", "", "", "", ""},
}
}

func (m *model) handleIncomingData(frameType, tag string, data []byte) {
m.incomingMutex.Lock()
defer m.incomingMutex.Unlock()

m.feederSources[tag]++

switch frameType {
case "beast":
frame, err := beast.NewFrame(data, false)
if nil != err {
return
}
m.incomingIcaoFrames[frame.Icao()]++
m.numIncomingBeast++
case "avr":
frame, err := mode_s.DecodeString(string(data), m.startTime)
if nil == err {
return
}
m.incomingIcaoFrames[frame.Icao()]++
m.numIncomingAvr++
case "sbs1":
frame := sbs1.NewFrame(string(data))
m.incomingIcaoFrames[frame.Icao()]++
m.numIncomingSbs1++
}
}

func (si *sourceInfo) init() {
si.planes = make(map[string]*export.PlaneLocation)
si.frames = make(map[string]uint64)
}

func (si *sourceInfo) update(loc *export.PlaneLocation) {
si.mu.Lock()
defer si.mu.Unlock()
if _, ok := si.frames[loc.Icao]; !ok {
si.icaos = append(si.icaos, loc.Icao)
sort.Strings(si.icaos)
}
si.frameCount++
si.planes[loc.Icao] = loc
si.frames[loc.Icao]++
}

func (si *sourceInfo) numFrames() string {
si.mu.Lock()
defer si.mu.Unlock()
return strconv.FormatUint(si.frameCount, 10)
}

func (si *sourceInfo) numFramesFor(icao string) string {
si.mu.Lock()
defer si.mu.Unlock()
return strconv.FormatUint(si.frames[icao], 10)
}

func (si *sourceInfo) getLoc(icao string) *export.PlaneLocation {
si.mu.Lock()
defer si.mu.Unlock()
p := si.planes[icao]
if nil == p {
return nil
}
return p
}

func (ps planesSource) String() string {
switch ps {
case planesSourceIncoming:
return "Incoming Data"
case planesSourceIngest:
return "After Ingest"
case planesSourceEnriched:
return "After Enrichment"
case planesSourceRoutedLow:
return "After Routing - Low"
case planesSourceRoutedHigh:
return "After Routing - High"
case planesSourceWSLow:
return "Websocket - Low"
case planesSourceWSHigh:
return "Websocket - High"
}
return "Unknown"
}
Loading

0 comments on commit 9bf12b0

Please sign in to comment.