Repository: evcc-io/evcc (go)

Issue:
Batterie-Ladestand auf Hauptseite
**Is your feature request related to a problem? Please describe.**
Als Nutzer muss ich in ein Untermenü, um den Ladestand des Hausakkus zu sehen.

**Describe the solution you'd like**
Als Nutzer will ich den Ladestand des Hausakkus transparent auf den ersten Blick sehen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Die Startseite zeigt den aktuellen Energiefluss und extrem prominent das Ladeszenario. Der aktuelle Ladestand des Hausakkus könnte zB sehr einfach hinter dem Batteriesymbol angezeigt werden.

<img width="1115" alt="Bildschirmfoto 2024-05-14 um 10 20 05" src="https://github.com/evcc-io/evcc/assets/14977978/0775da13-5c4d-4639-8520-d534b83ea2d6">


Pull request description:
UI: higher precision battery icon
fixes #13901

- 🪫 changed battery icon from five-step (`empty`, `quarter`, `half`, `three-quarter`, `full`) to continuous levels based on `soc`.
- 🔎 slightly increased battery level area inside icon

**battery levels**

[battery-animation.webm](https://github.com/evcc-io/evcc/assets/152287/d5d74239-6934-4bfc-8f66-c48f73202ce5)

**increased height**

<img src="https://github.com/evcc-io/evcc/assets/152287/137e8a8c-0ce3-4a8a-b037-5712a78a557d" width="150" >
 

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 6369a2f2..ee6d9976 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -16,7 +16,6 @@ linters:
   enable:
     - dogsled
     - durationcheck
-    - exportloopref
     - gci
     - gofmt
     - goimports
@@ -40,6 +39,7 @@ linters:
     # fixme
     # - bodyclose
     # - exhaustive
+    # - exportloopref
     # - gocritic
     # - godot
     # - gomoddirectives
diff --git a/assets/js/components/Energyflow/BatteryIcon.vue b/assets/js/components/Energyflow/BatteryIcon.vue
index c3199d8c..cc961ec6 100644
--- a/assets/js/components/Energyflow/BatteryIcon.vue
+++ b/assets/js/components/Energyflow/BatteryIcon.vue
@@ -1,13 +1,15 @@
 <template>
-	<component :is="`shopicon-regular-${icon}`"></component>
+	<svg :style="svgStyle" viewBox="0 0 48 48">
+		<path d="M0-.004h48v48H0v-48z" fill="none" />
+		<path
+			d="M35 9.996h-3v-4a2 2 0 00-2-2H18a2 2 0 00-2 2v4h-3a2 2 0 00-2 2v30a2 2 0 002 2h22a2 2 0 002-2v-30a2 2 0 00-2-2zm-15-2h8v2h-8v-2zm13 32H15v-26h18v26z"
+		/>
+		<path :d="socRect" />
+	</svg>
 </template>
 
 <script>
-import "@h2d2/shopicons/es/regular/batteryfull";
-import "@h2d2/shopicons/es/regular/batterythreequarters";
-import "@h2d2/shopicons/es/regular/batteryhalf";
-import "@h2d2/shopicons/es/regular/batteryquarter";
-import "@h2d2/shopicons/es/regular/batteryempty";
+import icon from "../../mixins/icon";
 
 export default {
 	name: "BatteryIcon",
@@ -15,13 +17,11 @@ export default {
 		soc: { type: Number, default: 0 },
 	},
 	computed: {
-		icon: function () {
-			if (this.soc > 80) return "batteryfull";
-			if (this.soc > 60) return "batterythreequarters";
-			if (this.soc > 40) return "batteryhalf";
-			if (this.soc > 20) return "batteryquarter";
-			return "batteryempty";
+		socRect() {
+			const height = (this.soc / (100 / 22)).toFixed(2);
+			return `M30 38H18v-${height}h12v${height}z`;
 		},
 	},
+	mixins: [icon],
 };
 </script>
diff --git a/go.mod b/go.mod
index ef5dc3f5..afac8fe0 100644
--- a/go.mod
+++ b/go.mod
@@ -60,7 +60,7 @@ require (
 	github.com/libp2p/zeroconf/v2 v2.2.0
 	github.com/lorenzodonini/ocpp-go v0.18.0
 	github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
-	github.com/mabunixda/wattpilot v1.7.0
+	github.com/mabunixda/wattpilot v1.7.2
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/mitchellh/mapstructure v1.5.0
 	github.com/mlnoga/rct v0.1.2-0.20240421173556-1c5b75037e2f
@@ -128,9 +128,6 @@ require (
 	github.com/go-logfmt/logfmt v0.6.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
-	github.com/gobwas/httphead v0.1.0 // indirect
-	github.com/gobwas/pool v0.2.1 // indirect
-	github.com/gobwas/ws v1.4.0 // indirect
 	github.com/golang/mock v1.6.0 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/gorilla/websocket v1.5.1 // indirect
diff --git a/go.sum b/go.sum
index 40f51085..254b1776 100644
--- a/go.sum
+++ b/go.sum
@@ -194,12 +194,6 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
 github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
 github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
-github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
-github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
-github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
-github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
-github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
-github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@@ -397,8 +391,8 @@ github.com/lorenzodonini/ocpp-go v0.18.0/go.mod h1:ZynYDWGw6CslG3vyPuucLsy6AyE+h
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
-github.com/mabunixda/wattpilot v1.7.0 h1:dLbsfsZi8+H4m6DuQUQzCAmLqVbZOMHn+m31zi8OmWc=
-github.com/mabunixda/wattpilot v1.7.0/go.mod h1:cfndLU/u8ANvy/HKNrT4ShsaNhPhIHlCRrDY8SyETFA=
+github.com/mabunixda/wattpilot v1.7.2 h1:DZcyZSJvu3YTVhbQSKnr4w1T4zL3krKourKAekRaevA=
+github.com/mabunixda/wattpilot v1.7.2/go.mod h1:9Gre9Jt8rVak0snzzlwLHCLxGfz36EmJ98fhgJ+jYkw=
 github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
 github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
diff --git a/push/hub.go b/push/hub.go
index 9c5150ea..61f858fd 100644
--- a/push/hub.go
+++ b/push/hub.go
@@ -124,12 +124,12 @@ func (h *Hub) Run(events <-chan Event, valueChan chan util.Param) {
 			continue
 		}
 
+		if strings.TrimSpace(msg) == "" {
+			continue
+		}
+
 		for _, sender := range h.sender {
-			if strings.TrimSpace(msg) != "" {
-				go sender.Send(title, msg)
-			} else {
-				log.DEBUG.Printf("did not send empty message template for %s: %v", ev.Event, err)
-			}
+			go sender.Send(title, msg)
 		}
 	}
 }
diff --git a/util/format.go b/util/format.go
index 8f1cb5dc..4a26739b 100644
--- a/util/format.go
+++ b/util/format.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/42atomys/sprout"
+	"golang.org/x/exp/maps"
 )
 
 var re = regexp.MustCompile(`(?i)\${(\w+)(:([a-zA-Z0-9%.]+))?}`)
@@ -73,27 +74,29 @@ func ReplaceFormatted(s string, kv map[string]interface{}) (string, error) {
 		match, key, format := m[0], m[1], m[3]
 
 		// find key and replacement value
-		val, ok := kv[strings.ToLower(key)]
-		if !ok {
+		var val *any
+		for k, v := range kv {
+			if strings.EqualFold(k, key) {
+				val = &v
+				break
+			}
+		}
+
+		if val == nil {
 			wanted = append(wanted, key)
 			format = "%s"
-			val = "?"
+			val = PtrTo(any("?"))
 		}
 
 		// update all literal matches
-		new := FormatValue(format, val)
+		new := FormatValue(format, *val)
 		s = strings.ReplaceAll(s, match, new)
 	}
 
 	// return missing keys
 	if len(wanted) > 0 {
-		got := make([]string, 0)
-		for k := range kv {
-			got = append(got, k)
-		}
-
-		err = fmt.Errorf("wanted: %v, got: %v", wanted, got)
+		return "", fmt.Errorf("wanted: %v, got: %v", wanted, maps.Keys(kv))
 	}
 
-	return s, err
+	return s, nil
 }
diff --git a/vehicle/mercedes.go b/vehicle/mercedes.go
index 805fba20..cfb461ae 100644
--- a/vehicle/mercedes.go
+++ b/vehicle/mercedes.go
@@ -21,12 +21,13 @@ func init() {
 // NewMercedesFromConfig creates a new vehicle
 func NewMercedesFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 	cc := struct {
-		embed   `mapstructure:",squash"`
-		Tokens  Tokens
-		Account string
-		VIN     string
-		Cache   time.Duration
-		Region  string
+		embed    `mapstructure:",squash"`
+		Tokens   Tokens
+		User     string
+		Account_ string `mapstructure:"account"` // TODO deprecated
+		VIN      string
+		Cache    time.Duration
+		Region   string
 	}{
 		Cache: interval,
 	}
@@ -40,8 +41,12 @@ func NewMercedesFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		return nil, err
 	}
 
+	if cc.User == "" && cc.Account_ != "" {
+		cc.User = cc.Account_
+	}
+
 	log := util.NewLogger("mercedes").Redact(cc.Tokens.Access, cc.Tokens.Refresh)
-	identity, err := mercedes.NewIdentity(log, token, cc.Account, cc.Region)
+	identity, err := mercedes.NewIdentity(log, token, cc.User, cc.Region)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/util/format_test.go b/util/format_test.go
index da21fdcd..561da2bc 100644
--- a/util/format_test.go
+++ b/util/format_test.go
@@ -37,6 +37,7 @@ func TestReplace(t *testing.T) {
 		// regex tests
 		{"foo", true, "${foo}", "true"},
 		{"foo", true, "${Foo}", "true"},
+		{"Foo", true, "${foo}", "true"},
 		{"foo", "1", "abc${foo}${foo}", "abc11"},
 		{"foo", math.Pi, "${foo:%.2f}", "3.14"},
 		{"foo", math.Pi, "${foo:%.0f}%", "3%"},