Server 2.4.0, app 2.11.1: ABRP forwarding removed from the server

The car sends live data to ABRP itself, and the car and the server go
over the same mobile connection: when the car cannot reach ABRP, it
cannot reach the server either, so the server's fallback bought
nothing. The forwarder, its ABRP_* settings and abrpSentUtc are gone;
the server only stores. The app stops sending abrpSentUtc (the server
ignores unknown fields, so 2.11.0 keeps working) and its texts no longer
promise a server fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
2026-10-18 12:49:46 +02:00
co-authored by Claude Opus 5.5
parent ca2f899403
commit 9f184362f0
18 changed files with 70 additions and 359 deletions
+20 -40
View File
@@ -1,5 +1,6 @@
// Command server is the Nils' MG4 car server: the API the car writes its telemetry,
// consumption and charging sessions to, stored in InfluxDB 2 and forwarded live to ABRP.
// consumption and charging sessions to, stored in InfluxDB 2. The car sends live data to ABRP
// itself.
// Dashboards are Grafana's job; Grafana reads the same bucket directly.
package main
@@ -11,7 +12,6 @@ import (
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
)
@@ -22,33 +22,28 @@ const (
)
type config struct {
listenAddr string
influxURL string
influxToken string
influxOrg string
influxBucket string
ingestToken string
abrpAPIKey string
abrpUserToken string
abrpMinInterval time.Duration
approvalPass string
publicURL string
listenAddr string
influxURL string
influxToken string
influxOrg string
influxBucket string
ingestToken string
approvalPass string
publicURL string
}
// loadConfig reads the environment and fails closed: a missing InfluxDB setting or a
// short ingest token stops the server from starting rather than serving something broken.
func loadConfig(getenv func(string) string) (config, error) {
c := config{
listenAddr: getenv("LISTEN_ADDR"),
influxURL: getenv("INFLUX_URL"),
influxToken: getenv("INFLUX_TOKEN"),
influxOrg: getenv("INFLUX_ORG"),
influxBucket: getenv("INFLUX_BUCKET"),
ingestToken: getenv("INGEST_TOKEN"),
abrpAPIKey: getenv("ABRP_API_KEY"),
abrpUserToken: getenv("ABRP_USER_TOKEN"),
approvalPass: getenv("APPROVAL_PASSWORD"),
publicURL: getenv("PUBLIC_URL"),
listenAddr: getenv("LISTEN_ADDR"),
influxURL: getenv("INFLUX_URL"),
influxToken: getenv("INFLUX_TOKEN"),
influxOrg: getenv("INFLUX_ORG"),
influxBucket: getenv("INFLUX_BUCKET"),
ingestToken: getenv("INGEST_TOKEN"),
approvalPass: getenv("APPROVAL_PASSWORD"),
publicURL: getenv("PUBLIC_URL"),
}
if c.listenAddr == "" {
c.listenAddr = ":8080"
@@ -69,14 +64,6 @@ func loadConfig(getenv func(string) string) (config, error) {
if c.approvalPass != "" && len(c.approvalPass) < minApprovalPasswordLength {
return config{}, fmt.Errorf("APPROVAL_PASSWORD must be at least %d characters", minApprovalPasswordLength)
}
c.abrpMinInterval = 5 * time.Second
if raw := getenv("ABRP_MIN_INTERVAL_SECONDS"); raw != "" {
seconds, err := strconv.ParseFloat(raw, 64)
if err != nil || seconds < 0 {
return config{}, errors.New("ABRP_MIN_INTERVAL_SECONDS must be a non-negative number")
}
c.abrpMinInterval = time.Duration(seconds * float64(time.Second))
}
return c, nil
}
@@ -90,12 +77,6 @@ func main() {
store := NewInfluxStore(cfg.influxURL, cfg.influxToken, cfg.influxOrg, cfg.influxBucket)
defer store.Close()
forwarder := &Forwarder{
APIKey: cfg.abrpAPIKey,
UserToken: cfg.abrpUserToken,
MinInterval: cfg.abrpMinInterval,
Client: &http.Client{Timeout: 10 * time.Second},
}
// Paired cars' tokens must be known before the first request; starting without them
// would answer every paired car with 401, so a failed load stops the start instead.
loadCtx, cancelLoad := context.WithTimeout(context.Background(), 30*time.Second)
@@ -106,8 +87,7 @@ func main() {
os.Exit(1)
}
pairing := NewPairing(cfg.approvalPass, devices)
api := &API{Store: store, IngestToken: cfg.ingestToken, Forwarder: forwarder,
Pairing: pairing, PublicURL: cfg.publicURL}
api := &API{Store: store, IngestToken: cfg.ingestToken, Pairing: pairing, PublicURL: cfg.publicURL}
srv := &http.Server{
Addr: cfg.listenAddr,
@@ -122,7 +102,7 @@ func main() {
defer stop()
go func() {
slog.Info("listening", "addr", cfg.listenAddr, "bucket", cfg.influxBucket,
"abrp", forwarder.configured(), "pairing", pairing.Enabled(), "pairedDevices", len(devices))
"pairing", pairing.Enabled(), "pairedDevices", len(devices))
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
slog.Error("server failed", "error", err)
os.Exit(1)