210 lines
5.8 KiB
Go
210 lines
5.8 KiB
Go
// Package main implementiert das eigenständige Fyne-GUI-Fenster für die Spannungsüberwachung (Volt Monitor).
|
|
// Es liest die Messwerte einer dynamischen Anzahl von ADS1115-Platinen automatisch über gRPC-Streaming ein.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"sort"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/app"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/data/binding"
|
|
"fyne.io/fyne/v2/widget"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
|
|
shack "remote-shack/api"
|
|
"remote-shack/internal/config"
|
|
)
|
|
|
|
// VoltClientGUI verwaltet die dynamischen Anzeigen für alle gelieferten analogen Kanäle.
|
|
type VoltClientGUI struct {
|
|
window fyne.Window
|
|
telemetryCl shack.TelemetryServiceClient
|
|
mainContainer *fyne.Container
|
|
statusLabel *widget.Label
|
|
voltBinds map[int32]binding.String
|
|
progressBars map[int32]*widget.ProgressBar
|
|
uiBuilt bool
|
|
}
|
|
|
|
func main() {
|
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
log.Println("=== Starte Skalierbaren Remote-Shack Volt-Client ===")
|
|
|
|
cfg, err := config.LoadClientConfig("client-config.toml")
|
|
if err != nil {
|
|
log.Fatalf("Fehler beim Laden der client-config.toml: %v", err)
|
|
}
|
|
|
|
conn, err := grpc.Dial(cfg.Client.ServerAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
log.Fatalf("Verbindung zum gRPC-Server fehlgeschlagen: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
telemetryClient := shack.NewTelemetryServiceClient(conn)
|
|
|
|
myApp := app.NewWithID("de.remote-shack.client.volt")
|
|
myWindow := myApp.NewWindow("Remote-Shack: Volt-Monitor")
|
|
|
|
g := &VoltClientGUI{
|
|
window: myWindow,
|
|
telemetryCl: telemetryClient,
|
|
voltBinds: make(map[int32]binding.String),
|
|
progressBars: make(map[int32]*widget.ProgressBar),
|
|
uiBuilt: false,
|
|
}
|
|
|
|
g.statusLabel = widget.NewLabelWithStyle("Status: Synchronisiere...", fyne.TextAlignCenter, fyne.TextStyle{Italic: true})
|
|
g.mainContainer = container.NewVBox(
|
|
widget.NewLabelWithStyle("--- TELEMETRIE (0..12 V) ---", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
|
)
|
|
|
|
g.window.SetContent(container.NewBorder(nil, container.NewVBox(widget.NewSeparator(), g.statusLabel), nil, nil, g.mainContainer))
|
|
g.window.Resize(fyne.NewSize(340, 500))
|
|
|
|
go g.startTelemetryStreamLoop()
|
|
|
|
myWindow.ShowAndRun()
|
|
}
|
|
|
|
// buildDynamicUI generiert die Text- und Balkenanzeigen dynamisch passend zur empfangenen Kanalanzahl.
|
|
func (g *VoltClientGUI) buildDynamicUI(channels []int32, labels map[int32]string, isSim bool) {
|
|
for _, ch := range channels {
|
|
g.voltBinds[ch] = binding.NewString()
|
|
|
|
// FIX: Präfix bei aktiver Simulation für maximale Klarheit im Shack
|
|
prefix := ""
|
|
if isSim {
|
|
prefix = "[SIM] "
|
|
}
|
|
_ = g.voltBinds[ch].Set(fmt.Sprintf("%s%s: --.- V", prefix, labels[ch]))
|
|
|
|
bar := widget.NewProgressBar()
|
|
bar.Min = 0.0
|
|
bar.Max = 12.0
|
|
g.progressBars[ch] = bar
|
|
|
|
g.mainContainer.Add(widget.NewLabelWithData(g.voltBinds[ch]))
|
|
g.mainContainer.Add(bar)
|
|
}
|
|
g.mainContainer.Refresh()
|
|
g.uiBuilt = true
|
|
}
|
|
|
|
// startTelemetryStreamLoop empfängt die berechnetenspannungswerte fortlaufend vom Server oder simuliert sie lokal.
|
|
func (g *VoltClientGUI) startTelemetryStreamLoop() {
|
|
dataChan := make(chan *shack.VoltageResponse, 1)
|
|
errChan := make(chan error, 1)
|
|
|
|
demoLabels := map[int32]string{
|
|
1: "Hauptstrom 13.8V",
|
|
2: "Logikbus 5V",
|
|
3: "Relais-Schiene",
|
|
4: "Rauschen (Frei)",
|
|
5: "Backup-Batterie",
|
|
}
|
|
|
|
go func() {
|
|
stream, err := g.telemetryCl.StreamVoltages(context.Background(), &shack.Empty{})
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
for {
|
|
res, err := stream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
dataChan <- res
|
|
}
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case res := <-dataChan:
|
|
if !g.uiBuilt && res.Channels != nil && len(res.Channels) > 0 {
|
|
var sortedChannels []int32
|
|
labels := make(map[int32]string)
|
|
for ch, reading := range res.Channels {
|
|
sortedChannels = append(sortedChannels, ch)
|
|
labels[ch] = reading.Label
|
|
}
|
|
sort.Slice(sortedChannels, func(i, j int) bool { return sortedChannels[i] < sortedChannels[j] })
|
|
|
|
fyne.Do(func() { g.buildDynamicUI(sortedChannels, labels, false) })
|
|
}
|
|
|
|
fyne.Do(func() {
|
|
g.statusLabel.SetText("Verbindung: Empfange Live-Daten")
|
|
for ch, reading := range res.Channels {
|
|
if bar, ok := g.progressBars[ch]; ok {
|
|
val := reading.Voltage
|
|
if val < 0 { val = 0 }
|
|
if val > 12 { val = 12 }
|
|
_ = g.voltBinds[ch].Set(fmt.Sprintf("%s: %.2f V", reading.Label, val))
|
|
bar.SetValue(float64(val))
|
|
}
|
|
}
|
|
})
|
|
|
|
case <-errChan:
|
|
g.runLocalSimulation(demoLabels)
|
|
return
|
|
|
|
case <-time.After(2 * time.Second):
|
|
if !g.uiBuilt {
|
|
log.Println("[Volt-Monitor] Keine Serverdaten empfangen. Erzwinge lokalen Simulator...")
|
|
g.runLocalSimulation(demoLabels)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// runLocalSimulation übernimmt die regelmäßige Aktualisierung der GUI mit Dummy-Messwerten.
|
|
func (g *VoltClientGUI) runLocalSimulation(demoLabels map[int32]string) {
|
|
if !g.uiBuilt {
|
|
fyne.Do(func() {
|
|
// Übergibt 'true' für isSim an das UI-Builder-Subsystem
|
|
g.buildDynamicUI([]int32{1, 2, 3, 4, 5}, demoLabels, true)
|
|
})
|
|
}
|
|
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
for {
|
|
fyne.Do(func() {
|
|
// FIX: Markanter Warntext unten im Fenster platziert
|
|
g.statusLabel.SetText("⚠️ SIMULATION (KEINE HARDWARE)")
|
|
|
|
var ch int32
|
|
for ch = 1; ch <= 5; ch++ {
|
|
var val float32
|
|
switch ch {
|
|
case 1:
|
|
val = 13.80 + (r.Float32() * 0.2 - 0.1)
|
|
case 2:
|
|
val = 5.02 + (r.Float32() * 0.04 - 0.02)
|
|
case 5:
|
|
val = 9.15 + (r.Float32() * 0.08 - 0.04)
|
|
default:
|
|
val = r.Float32() * 12.0
|
|
}
|
|
|
|
if val < 0 { val = 0 }
|
|
if val > 12 { val = 12 }
|
|
|
|
_ = g.voltBinds[ch].Set(fmt.Sprintf("[SIM] %s: %.2f V", demoLabels[ch], val))
|
|
g.progressBars[ch].SetValue(float64(val))
|
|
}
|
|
})
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|