remote-shack/internal/server/telemetry.go
2026-06-13 13:02:54 +02:00

111 lines
3.1 KiB
Go

// Package server implementiert die gRPC-Dienste für das Remote-Shack-System.
package server
import (
"context"
"log"
"math/rand"
"strconv"
"strings"
"time"
shack "remote-shack/api"
"remote-shack/internal/config"
)
// VerifiedModule speichert die geparste Hardware-Adresse und die zugehörigen Kanallabels.
type VerifiedModule struct {
Address int64
Labels []string
}
// TelemetryServer verwaltet die I2C-Abfragen einer dynamischen Anzahl von ADS1115-Platinen.
type TelemetryServer struct {
shack.UnimplementedTelemetryServiceServer
modules []VerifiedModule
}
// NewTelemetryServer initialisiert den Server und parst die I2C-Parameter sowie Labels aus der Konfiguration.
func NewTelemetryServer(cfg *config.ServerConfig) *TelemetryServer {
var verifiedMods []VerifiedModule
for i, mod := range cfg.Server.Modules {
if i >= 4 {
log.Println("[Hardware-Warnung] ADS1115 limitiert auf maximal 4 Platinen am Bus. Ignoriere weitere Einträge.")
break
}
addr, err := strconv.ParseInt(strings.TrimPrefix(mod.Address, "0x"), 16, 64)
if err != nil {
log.Printf("[Hardware-Fehler] Modul-Adresse '%s' ungültig: %v", mod.Address, err)
continue
}
if addr < 0 || addr > 127 {
log.Printf("[Hardware-Fehler] I2C-Adresse 0x%02x außerhalb des 7-Bit-Spektrums (0..127)", addr)
continue
}
// Sicherstellen, dass exakt 4 Labels vorliegen, notfalls mit Standardtext auffüllen
labels := mod.Labels
for len(labels) < 4 {
labels = append(labels, "Unbekannt")
}
verifiedMods = append(verifiedMods, VerifiedModule{
Address: addr,
Labels: labels,
})
log.Printf("[Hardware] Modul %d auf I2C 0x%02x mit Labels %v registriert.", i+1, addr, labels[:4])
}
return &TelemetryServer{modules: verifiedMods}
}
// StreamVoltages sendet fortlaufend die gemessenen Spannungen samt Konfigurationslabels an den Client.
func (s *TelemetryServer) StreamVoltages(req *shack.Empty, stream shack.TelemetryService_StreamVoltagesServer) error {
log.Printf("[Telemetrie] Volt-Monitor verbunden. Stream startet für %d Modul(e)...", len(s.modules))
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for {
select {
case <-stream.Context().Done():
log.Println("[Telemetrie] Volt-Monitor-Client getrennt.")
return nil
case <-ticker.C:
chData := make(map[int32]*shack.ChannelReading)
var globalChannel int32 = 1
for _, mod := range s.modules {
var boardChannel int32
for boardChannel = 0; boardChannel < 4; boardChannel++ {
var simulatedVolt float32
switch globalChannel {
case 1:
simulatedVolt = 13.80 + (r.Float32() * 0.2 - 0.1)
case 2:
simulatedVolt = 5.02 + (r.Float32() * 0.06 - 0.03)
case 5:
simulatedVolt = 9.15 + (r.Float32() * 0.1 - 0.05)
default:
simulatedVolt = r.Float32() * 12.0
}
chData[globalChannel] = &shack.ChannelReading{
Voltage: simulatedVolt,
Label: mod.Labels[boardChannel],
}
globalChannel++
}
}
err := stream.Send(&shack.VoltageResponse{Channels: chData})
if err != nil {
return err
}
}
}
}