Repository: evcc-io/evcc
Pull request #9077 (2023-07-23 08:13:20)

ABL: clarify eMH versions
Refs https://github.com/evcc-io/evcc/issues/9017#issuecomment-1645313205

@premultiply wissen wir irgendwas über das Registerlayout der TWIN? Dann könnte ich das hier ergänzen.


Patch:
diff --git a/charger/homewizard.go b/charger/homewizard.go
new file mode 100644
index 00000000..16c1f604
--- /dev/null
+++ b/charger/homewizard.go
@@ -0,0 +1,100 @@
+package charger
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/evcc-io/evcc/api"
+	"github.com/evcc-io/evcc/meter/homewizard"
+	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/request"
+)
+
+// HomeWizard project homepage
+// https://homewizard-energy-api.readthedocs.io/index.html
+
+// HomeWizard charger implementation
+type HomeWizard struct {
+	conn *homewizard.Connection
+	*switchSocket
+}
+
+func init() {
+	registry.Add("homewizard", NewHomeWizardFromConfig)
+}
+
+// NewHomeWizardFromConfig creates a HomeWizard charger from generic config
+func NewHomeWizardFromConfig(other map[string]interface{}) (api.Charger, error) {
+	var cc = struct {
+		embed        `mapstructure:",squash"`
+		URI          string
+		StandbyPower float64
+	}{}
+
+	if err := util.DecodeOther(other, &cc); err != nil {
+		return nil, err
+	}
+
+	return NewHomeWizard(cc.embed, cc.URI, cc.StandbyPower)
+}
+
+// NewHomeWizard creates HomeWizard charger
+func NewHomeWizard(embed embed, uri string, standbypower float64) (*HomeWizard, error) {
+	conn, err := homewizard.NewConnection(uri)
+	if err != nil {
+		return nil, err
+	}
+
+	c := &HomeWizard{
+		conn: conn,
+	}
+
+	// Check compatible product type
+	if c.conn.ProductType != "HWE-SKT" {
+		return nil, errors.New("not supported product type: " + c.conn.ProductType)
+	}
+
+	c.switchSocket = NewSwitchSocket(&embed, c.Enabled, c.conn.CurrentPower, standbypower)
+
+	return c, nil
+}
+
+// Enabled implements the api.Charger interface
+func (c *HomeWizard) Enabled() (bool, error) {
+	var res homewizard.StateResponse
+	err := c.conn.GetJSON(fmt.Sprintf("%s/data", c.conn.URI), &res)
+	return res.PowerOn, err
+}
+
+// Enable implements the api.Charger interface
+func (c *HomeWizard) Enable(enable bool) error {
+	var res homewizard.StateResponse
+	data := map[string]interface{}{
+		"power_on": enable,
+	}
+
+	req, err := request.New(http.MethodPut, fmt.Sprintf("%s/state", c.conn.URI), request.MarshalJSON(data), request.JSONEncoding)
+	if err != nil {
+		return err
+	}
+	if err := c.conn.DoJSON(req, &res); err != nil {
+		return err
+	}
+
+	switch {
+	case enable && !res.PowerOn:
+		return errors.New("switchOn failed")
+	case !enable && res.PowerOn:
+		return errors.New("switchOff failed")
+	default:
+		return nil
+	}
+}
+
+var _ api.MeterEnergy = (*HomeWizard)(nil)
+
+// TotalEnergy implements the api.MeterEnergy interface
+func (c *HomeWizard) TotalEnergy() (float64, error) {
+	return c.conn.TotalEnergy()
+}
diff --git a/cmd/configure/localization/de.toml b/cmd/configure/localization/de.toml
index 672923d9..598e31fc 100644
--- a/cmd/configure/localization/de.toml
+++ b/cmd/configure/localization/de.toml
@@ -6,7 +6,7 @@ Flow_Type = "Was möchtest du machen?"
 Flow_Type_NewConfiguration = "Eine neue evcc Konfigurationsdatei erstellen"
 Flow_Type_SingleDevice = "Ein einzelnes Gerät konfigurieren (muss manuell in eine Konfigurationsdatei eingetragen werden!)"
 Flow_NewConfiguration_Setup = "- Hausinstallation einrichten"
-Flow_NewConfiguration_Select = "Wähle eines der folgenden PV Komplettsysteme aus, oder '{{ .ItemNotPresent }}' falls keines dieser Geräte vorhanden ist"
+Flow_NewConfiguration_Select = "Wähle eines der folgenden integrierten PV-Systeme aus, oder '{{ .ItemNotPresent }}' falls es kein integriertes PV-System vorhanden ist"
 Flow_SingleDevice_Setup = "- Ein Gerät konfigurieren"
 Flow_SingleDevice_Select = "Wähle eine der folgenden Gerätekategorien aus:"
 Flow_SingleDevice_Config = "Die Konfiguration lautet:"
@@ -31,7 +31,7 @@ Choose = "Wähle"
 Category_ChargerTitle = "Wallbox"
 Category_ChargerArticle = "eine"
 Category_ChargerAdditional = "eine weitere"
-Category_SystemTitle = "PV System"
+Category_SystemTitle = "integriertes PV-System"
 Category_SystemArticle = "ein"
 Category_SystemAdditional = "ein weiteres"
 Category_GridMeterTitle = "Netz-Stromzähler"
diff --git a/cmd/configure/localization/en.toml b/cmd/configure/localization/en.toml
index f32d6cba..f6b17843 100644
--- a/cmd/configure/localization/en.toml
+++ b/cmd/configure/localization/en.toml
@@ -6,7 +6,7 @@ Flow_Type = "What do you want to do?"
 Flow_Type_NewConfiguration = "Create a new evcc configuration file"
 Flow_Type_SingleDevice = "Configure a single device (has to be added manually to a configuration file!)"
 Flow_NewConfiguration_Setup = "- Setup meters (house installation)"
-Flow_NewConfiguration_Select = "Choose one of the following PV systems, or '{{ .ItemNotPresent }}' if you have none of them"
+Flow_NewConfiguration_Select = "Choose one of the following integrated PV systems, or '{{ .ItemNotPresent }}' if you have none of them"
 Flow_SingleDevice_Setup = "- Setup a device"
 Flow_SingleDevice_Select = "Choose one of the following device categories"
 Flow_SingleDevice_Config = "The configuration:"
@@ -31,8 +31,8 @@ Choose = "Choose"
 Category_ChargerTitle = "wallbox"
 Category_ChargerArticle = "a"
 Category_ChargerAdditional = "one more"
-Category_SystemTitle = "PV system"
-Category_SystemArticle = "a"
+Category_SystemTitle = "integrated PV system"
+Category_SystemArticle = "an"
 Category_SystemAdditional = "one more"
 Category_GridMeterTitle = "grid meter"
 Category_GridMeterArticle = "a"
diff --git a/meter/homewizard.go b/meter/homewizard.go
new file mode 100644
index 00000000..9c354d37
--- /dev/null
+++ b/meter/homewizard.go
@@ -0,0 +1,58 @@
+package meter
+
+import (
+	"github.com/evcc-io/evcc/api"
+	"github.com/evcc-io/evcc/meter/homewizard"
+	"github.com/evcc-io/evcc/util"
+)
+
+// HomeWizard meter implementation
+type HomeWizard struct {
+	conn *homewizard.Connection
+}
+
+// HomeWizard meter implementation
+func init() {
+	registry.Add("homewizard", NewHomeWizardFromConfig)
+}
+
+// NewHomeWizardFromConfig creates a HomeWizard meter from generic config
+func NewHomeWizardFromConfig(other map[string]interface{}) (api.Meter, error) {
+	cc := struct {
+		URI string
+	}{}
+
+	if err := util.DecodeOther(other, &cc); err != nil {
+		return nil, err
+	}
+
+	return NewHomeWizard(cc.URI)
+}
+
+// NewHomeWizard creates HomeWizard meter
+func NewHomeWizard(uri string) (*HomeWizard, error) {
+	conn, err := homewizard.NewConnection(uri)
+	if err != nil {
+		return nil, err
+	}
+
+	c := &HomeWizard{
+		conn: conn,
+	}
+
+	return c, err
+}
+
+var _ api.Meter = (*HomeWizard)(nil)
+
+// CurrentPower implements the api.Meter interface
+func (c *HomeWizard) CurrentPower() (float64, error) {
+	return c.conn.CurrentPower()
+}
+
+var _ api.MeterEnergy = (*HomeWizard)(nil)
+
+// TotalEnergy implements the api.MeterEnergy interface
+func (c *HomeWizard) TotalEnergy() (float64, error) {
+	return c.conn.TotalEnergy()
+}
diff --git a/meter/homewizard/connection.go b/meter/homewizard/connection.go
new file mode 100644
index 00000000..4e0a8bbc
--- /dev/null
+++ b/meter/homewizard/connection.go
@@ -0,0 +1,61 @@
+package homewizard
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/request"
+	"github.com/evcc-io/evcc/util/transport"
+)
+
+// Connection is the homewizard connection
+type Connection struct {
+	*request.Helper
+	URI         string
+	ProductType string
+}
+
+// NewConnection creates a homewizard connection
+func NewConnection(uri string) (*Connection, error) {
+	if uri == "" {
+		return nil, errors.New("missing uri")
+	}
+
+	log := util.NewLogger("homewizard")
+	c := &Connection{
+		Helper: request.NewHelper(log),
+		URI:    fmt.Sprintf("%s/api", util.DefaultScheme(strings.TrimRight(uri, "/"), "http")),
+	}
+
+	c.Client.Transport = request.NewTripper(log, transport.Insecure())
+
+	// Check and set API version + product type
+	var res ApiResponse
+	if err := c.GetJSON(c.URI, &res); err != nil {
+		return c, err
+	}
+	if res.ApiVersion != "v1" {
+		return nil, errors.New("not supported api version: " + res.ApiVersion)
+	}
+
+	c.URI = c.URI + "/" + res.ApiVersion
+	c.ProductType = res.ProductType
+
+	return c, nil
+}
+
+// CurrentPower implements the api.Meter interface
+func (d *Connection) CurrentPower() (float64, error) {
+	var res DataResponse
+	err := d.GetJSON(fmt.Sprintf("%s/data", d.URI), &res)
+	return res.ActivePowerW, err
+}
+
+// TotalEnergy implements the api.MeterEnergy interface
+func (d *Connection) TotalEnergy() (float64, error) {
+	var res DataResponse
+	err := d.GetJSON(fmt.Sprintf("%s/data", d.URI), &res)
+	return res.TotalPowerImportT1kWh + res.TotalPowerImportT2kWh + res.TotalPowerImportT3kWh + res.TotalPowerImportT4kWh, err
+}
diff --git a/meter/homewizard/types.go b/meter/homewizard/types.go
new file mode 100644
index 00000000..a0039fa7
--- /dev/null
+++ b/meter/homewizard/types.go
@@ -0,0 +1,24 @@
+package homewizard
+
+// ApiResponse returns allows you to get basic information from the HomeWizard Energy Socket
+// https://homewizard-energy-api.readthedocs.io/endpoints.html#basic-information-api
+type ApiResponse struct {
+	ProductType string `json:"product_type"`
+	ApiVersion  string `json:"api_version"`
+}
+
+// StateResponse returns the actual state of the HomeWizard Energy Socket
+// https://homewizard-energy-api.readthedocs.io/endpoints.html#recent-measurement-api-v1-data
+type StateResponse struct {
+	PowerOn bool `json:"power_on"`
+}
+
+// DataResponse returns the most recent measurements from the HomeWizard device
+// https://homewizard-energy-api.readthedocs.io/endpoints.html#state-api-v1-state
+type DataResponse struct {
+	ActivePowerW          float64 `json:"active_power_w"`
+	TotalPowerImportT1kWh float64 `json:"total_power_import_t1_kwh"`
+	TotalPowerImportT2kWh float64 `json:"total_power_import_t2_kwh"`
+	TotalPowerImportT3kWh float64 `json:"total_power_import_t3_kwh"`
+	TotalPowerImportT4kWh float64 `json:"total_power_import_t4_kwh"`
+}
diff --git a/templates/definition/charger/abl.yaml b/templates/definition/charger/abl.yaml
index 237e9e6e..390ce537 100644
--- a/templates/definition/charger/abl.yaml
+++ b/templates/definition/charger/abl.yaml
@@ -2,7 +2,7 @@ template: abl
 products:
   - brand: ABL
     description:
-      generic: eMH
+      generic: eMH1, eMH2
   - brand: SENEC
     description:
       generic: Wallbox pro
diff --git a/templates/definition/charger/homewizard.yaml b/templates/definition/charger/homewizard.yaml
new file mode 100644
index 00000000..fd839424
--- /dev/null
+++ b/templates/definition/charger/homewizard.yaml
@@ -0,0 +1,10 @@
+template: homewizard
+products:
+  - brand: HomeWizard
+group: switchsockets
+params:
+  - name: host
+  - preset: switchsocket
+render: |
+  type: homewizard
+  uri: http://{{ .host }}
diff --git a/templates/definition/charger/tasmota.yaml b/templates/definition/charger/tasmota.yaml
index fe3e2d46..2ef4c522 100644
--- a/templates/definition/charger/tasmota.yaml
+++ b/templates/definition/charger/tasmota.yaml
@@ -3,7 +3,6 @@ products:
   - brand: Tasmota
 group: switchsockets
 params:
-  - name: host
   - name: host
   - name: user
     required: false
diff --git a/templates/definition/meter/homewizard.yaml b/templates/definition/meter/homewizard.yaml
index 8d33094c..6a292160 100644
--- a/templates/definition/meter/homewizard.yaml
+++ b/templates/definition/meter/homewizard.yaml
@@ -5,22 +5,9 @@ products:
       generic: Wi-Fi P1 Meter
 params:
   - name: usage
-    choice: ["grid"]
+    choice: ["grid", "pv"]
   - name: host
     required: true
 render: |
-  type: custom
-  power:
-    source: http
-    uri: http://{{ .host }}/api/v1/data
-    method: GET
-    headers:
-      - content-type: application/json
-    jq: .active_power_w
-  energy:
-    source: http
-    uri: http://{{ .host }}/api/v1/data
-    method: GET
-    headers:
-      - content-type: application/json
-    jq: '[to_entries[] | select(.key|startswith("total_power_import_t")) | .value] | add'
+  type: homewizard
+  uri: http://{{ .host }}
diff --git a/templates/definition/meter/mtec-eb-gen2.yaml b/templates/definition/meter/mtec-eb-gen2.yaml
index a465f14d..647ed055 100644
--- a/templates/definition/meter/mtec-eb-gen2.yaml
+++ b/templates/definition/meter/mtec-eb-gen2.yaml
@@ -8,6 +8,7 @@ params:
     choice: ["grid", "pv", "battery"]
   - name: modbus
     choice: ["rs485"]
+    id: 247
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -18,6 +19,7 @@ render: |
       address: 11000 # Zähler (NVP) Leistung
       type: holding
       decode: int32
+    scale: -1
   {{- end }}
   {{- if eq .usage "pv" }}
   power:
diff --git a/templates/definition/meter/mtec-eb-gen3.yaml b/templates/definition/meter/mtec-eb-gen3.yaml
index 34adb214..ca07ed93 100644
--- a/templates/definition/meter/mtec-eb-gen3.yaml
+++ b/templates/definition/meter/mtec-eb-gen3.yaml
@@ -8,6 +8,7 @@ params:
     choice: ["grid", "pv", "battery"]
   - name: modbus
     choice: ["rs485"]
+    id: 247
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -18,6 +19,7 @@ render: |
       address: 11000 # Zähler (NVP) Leistung
       type: holding
       decode: int32
+    scale: -1
   {{- end }}
   {{- if eq .usage "pv" }}
   power:
diff --git a/templates/definition/meter/slimmelezer.yaml b/templates/definition/meter/slimmelezer.yaml
new file mode 100644
index 00000000..b0b97e7a
--- /dev/null
+++ b/templates/definition/meter/slimmelezer.yaml
@@ -0,0 +1,115 @@
+template: slimmelezer
+products:
+  - brand: Zuidwijk
+    description:
+      generic: SlimmeLezer(+)
+  - brand: ESPHome
+    description:
+      generic: DSMR
+params:
+  - name: usage
+    choice: ["grid"]
+  - name: host
+    required: true
+render: |
+  type: custom
+  power:
+  - source: calc
+    add:
+    - source: http
+      uri: http://{{ .host }}/sensor/power_delivered
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: 1000
+    - source: http
+      uri: http://{{ .host }}/sensor/power_returned
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: -1000
+  energy:
+    source: calc
+    add:
+    - source: http
+      uri: http://{{ .host }}/sensor/energy_delivered_tariff1
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+    - source: http
+      uri: http://{{ .host }}/sensor/energy_delivered_tariff2
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+  currents:
+  - source: http
+    uri: http://{{ .host }}/sensor/current_l1
+    method: GET
+    headers:
+    - content-type: application/json
+    jq: .value
+  - source: http
+    uri: http://{{ .host }}/sensor/current_l2
+    method: GET
+    headers:
+    - content-type: application/json
+    jq: .value
+  - source: http
+    uri: http://{{ .host }}/sensor/current_l3
+    method: GET
+    headers:
+    - content-type: application/json
+    jq: .value
+  powers:
+  - source: calc
+    add:
+    - source: http
+      uri: http://{{ .host }}/sensor/power_delivered_l1
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: 1000
+    - source: http
+      uri: http://{{ .host }}/sensor/power_returned_l1
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: -1000
+  - source: calc
+    add:
+    - source: http
+      uri: http://{{ .host }}/sensor/power_delivered_l2
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: 1000
+    - source: http
+      uri: http://{{ .host }}/sensor/power_returned_l2
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: -1000
+  - source: calc
+    add:
+    - source: http
+      uri: http://{{ .host }}/sensor/power_delivered_l3
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: 1000
+    - source: http
+      uri: http://{{ .host }}/sensor/power_returned_l3
+      method: GET
+      headers:
+      - content-type: application/json
+      jq: .value
+      scale: -1000
diff --git a/templates/docs/charger/abl_0.yaml b/templates/docs/charger/abl_0.yaml
index eb39c8a5..a9f66285 100644
--- a/templates/docs/charger/abl_0.yaml
+++ b/templates/docs/charger/abl_0.yaml
@@ -1,6 +1,6 @@
 product:
   brand: ABL
-  description: eMH
+  description: eMH1, eMH2
 capabilities: ["mA"]
 requirements: ["sponsorship"]
 render:
diff --git a/templates/docs/charger/homewizard_0.yaml b/templates/docs/charger/homewizard_0.yaml
new file mode 100644
index 00000000..5b15f360
--- /dev/null
+++ b/templates/docs/charger/homewizard_0.yaml
@@ -0,0 +1,16 @@
+product:
+  brand: HomeWizard
+  group: Schaltbare Steckdosen
+render:
+  - default: |
+      type: template
+      template: homewizard
+      host: 192.0.2.2 # IP-Adresse oder Hostname
+      standbypower: 15 # Leistung oberhalb des angegebenen Wertes wird als Ladeleistung gewertet (Optional)
+    advanced: |
+      type: template
+      template: homewizard
+      host: 192.0.2.2 # IP-Adresse oder Hostname
+      standbypower: 15 # Leistung oberhalb des angegebenen Wertes wird als Ladeleistung gewertet (Optional)
+      integrateddevice: # Optional
+      icon: # Icon in der Benutzeroberfläche (Optional)
diff --git a/templates/docs/meter/homewizard_0.yaml b/templates/docs/meter/homewizard_0.yaml
index 4ccb23f8..fa0f12ca 100644
--- a/templates/docs/meter/homewizard_0.yaml
+++ b/templates/docs/meter/homewizard_0.yaml
@@ -8,3 +8,9 @@ render:
       template: homewizard
       usage: grid
       host: 192.0.2.2 # IP-Adresse oder Hostname
+  - usage: pv
+    default: |
+      type: template
+      template: homewizard
+      usage: pv
+      host: 192.0.2.2 # IP-Adresse oder Hostname
diff --git a/templates/docs/meter/mtec-eb-gen2_0.yaml b/templates/docs/meter/mtec-eb-gen2_0.yaml
index 5f76fb89..de33af61 100644
--- a/templates/docs/meter/mtec-eb-gen2_0.yaml
+++ b/templates/docs/meter/mtec-eb-gen2_0.yaml
@@ -10,14 +10,14 @@ render:
 
       # RS485 via adapter (Modbus RTU)
       modbus: rs485serial
-      id: 1
+      id: 247
       device: /dev/ttyUSB0 # USB-RS485 Adapter Adresse
       baudrate: 9600 # Prüfe die Geräteeinstellungen, typische Werte sind 9600, 19200, 38400, 57600, 115200
       comset: "8N1" # Kommunikationsparameter für den Adapter
 
       # RS485 via TCP/IP (Modbus RTU)
       modbus: rs485tcpip
-      id: 1
+      id: 247
       host: 192.0.2.2 # Hostname
       port: 502 # Port
   - usage: pv
@@ -28,14 +28,14 @@ render:
 
       # RS485 via adapter (Modbus RTU)
       modbus: rs485serial
-      id: 1
+      id: 247
       device: /dev/ttyUSB0 # USB-RS485 Adapter Adresse
       baudrate: 9600 # Prüfe die Geräteeinstellungen, typische Werte sind 9600, 19200, 38400, 57600, 115200
       comset: "8N1" # Kommunikationsparameter für den Adapter
 
       # RS485 via TCP/IP (Modbus RTU)
       modbus: rs485tcpip
-      id: 1
+      id: 247
       host: 192.0.2.2 # Hostname
       port: 502 # Port
   - usage: battery
@@ -46,13 +46,13 @@ render:
 
       # RS485 via adapter (Modbus RTU)
       modbus: rs485serial
-      id: 1
+      id: 247
       device: /dev/ttyUSB0 # USB-RS485 Adapter Adresse
       baudrate: 9600 # Prüfe die Geräteeinstellungen, typische Werte sind 9600, 19200, 38400, 57600, 115200
       comset: "8N1" # Kommunikationsparameter für den Adapter
 
       # RS485 via TCP/IP (Modbus RTU)
       modbus: rs485tcpip
-      id: 1
+      id: 247
       host: 192.0.2.2 # Hostname
       port: 502 # Port
diff --git a/templates/docs/meter/mtec-eb-gen3_0.yaml b/templates/docs/meter/mtec-eb-gen3_0.yaml
index 850d6ee2..05740fae 100644
--- a/templates/docs/meter/mtec-eb-gen3_0.yaml
+++ b/templates/docs/meter/mtec-eb-gen3_0.yaml
@@ -10,14 +10,14 @@ render:
 
       # RS485 via adapter (Modbus RTU)
       modbus: rs485serial
-      id: 1
+      id: 247
       device: /dev/ttyUSB0 # USB-RS485 Adapter Adresse
       baudrate: 9600 # Prüfe die Geräteeinstellungen, typische Werte sind 9600, 19200, 38400, 57600, 115200
       comset: "8N1" # Kommunikationsparameter für den Adapter
 
       # RS485 via TCP/IP (Modbus RTU)
       modbus: rs485tcpip
-      id: 1
+      id: 247
       host: 192.0.2.2 # Hostname
       port: 502 # Port
   - usage: pv
@@ -28,14 +28,14 @@ render:
 
       # RS485 via adapter (Modbus RTU)
       modbus: rs485serial
-      id: 1
+      id: 247
       device: /dev/ttyUSB0 # USB-RS485 Adapter Adresse
       baudrate: 9600 # Prüfe die Geräteeinstellungen, typische Werte sind 9600, 19200, 38400, 57600, 115200
       comset: "8N1" # Kommunikationsparameter für den Adapter
 
       # RS485 via TCP/IP (Modbus RTU)
       modbus: rs485tcpip
-      id: 1
+      id: 247
       host: 192.0.2.2 # Hostname
       port: 502 # Port
   - usage: battery
@@ -46,13 +46,13 @@ render:
 
       # RS485 via adapter (Modbus RTU)
       modbus: rs485serial
-      id: 1
+      id: 247
       device: /dev/ttyUSB0 # USB-RS485 Adapter Adresse
       baudrate: 9600 # Prüfe die Geräteeinstellungen, typische Werte sind 9600, 19200, 38400, 57600, 115200
       comset: "8N1" # Kommunikationsparameter für den Adapter
 
       # RS485 via TCP/IP (Modbus RTU)
       modbus: rs485tcpip
-      id: 1
+      id: 247
       host: 192.0.2.2 # Hostname
       port: 502 # Port
diff --git a/templates/docs/meter/slimmelezer_0.yaml b/templates/docs/meter/slimmelezer_0.yaml
new file mode 100644
index 00000000..05c20e14
--- /dev/null
+++ b/templates/docs/meter/slimmelezer_0.yaml
@@ -0,0 +1,10 @@
+product:
+  brand: Zuidwijk
+  description: SlimmeLezer(+)
+render:
+  - usage: grid
+    default: |
+      type: template
+      template: slimmelezer
+      usage: grid
+      host: 192.0.2.2 # IP-Adresse oder Hostname
diff --git a/templates/docs/meter/slimmelezer_1.yaml b/templates/docs/meter/slimmelezer_1.yaml
new file mode 100644
index 00000000..a7ecb3a4
--- /dev/null
+++ b/templates/docs/meter/slimmelezer_1.yaml
@@ -0,0 +1,10 @@
+product:
+  brand: ESPHome
+  description: DSMR
+render:
+  - usage: grid
+    default: |
+      type: template
+      template: slimmelezer
+      usage: grid
+      host: 192.0.2.2 # IP-Adresse oder Hostname
diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json
index e2d5b021..9fddff3a 100644
--- a/templates/evcc.io/brands.json
+++ b/templates/evcc.io/brands.json
@@ -67,6 +67,7 @@
  "SmartPlugs": [
   "AVM",
   "Homematic / Homematic IP",
+  "HomeWizard",
   "myStrom",
   "Shelly",
   "Tasmota",
@@ -85,6 +86,7 @@
   "E3/DC",
   "Eastron",
   "Enphase",
+  "ESPHome",
   "FENECON",
   "FoxESS",
   "Fronius",
@@ -128,7 +130,8 @@
   "Victron",
   "Volkszähler",
   "Youless",
-  "ZCS Azzurro"
+  "ZCS Azzurro",
+  "Zuidwijk"
  ],
  "PVBattery": [
   "Alpha ESS",
@@ -144,6 +147,7 @@
   "GoodWe",
   "Growatt",
   "Homematic IP",
+  "HomeWizard",
   "Hoymiles",
   "Huawei",
   "IGEN Tech",
diff --git a/util/oauth/helper.go b/util/oauth/helper.go
index c9692f98..15e16909 100644
--- a/util/oauth/helper.go
+++ b/util/oauth/helper.go
@@ -23,7 +23,9 @@ func Refresh(log *util.Logger, token *oauth2.Token, ts oauth2.TokenSource, optMa
 					log.ERROR.Printf("token refresh: %v, giving up", err)
 					return
 				}
+
 				log.ERROR.Printf("token refresh: %v", err)
+				continue
 			}
 
 			failed = 0
diff --git a/vehicle/volvo/connected/identity.go b/vehicle/volvo/connected/identity.go
index 57c22547..18a48aff 100644
--- a/vehicle/volvo/connected/identity.go
+++ b/vehicle/volvo/connected/identity.go
@@ -88,8 +88,10 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
 		return nil, err
 	}
 
-	var res oauth2.Token
-	err = v.DoJSON(req, &res)
+	var res oauth.Token
+	if err := v.DoJSON(req, &res); err != nil {
+		return nil, err
+	}
 
-	return &res, err
+	return (*oauth2.Token)(&res), err
 }


Test patch:
diff --git a/meter/homewizard/types_test.go b/meter/homewizard/types_test.go
new file mode 100644
index 00000000..f9e548e6
--- /dev/null
+++ b/meter/homewizard/types_test.go
@@ -0,0 +1,46 @@
+package homewizard
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// Test ApiResponse
+func TestUnmarshalApiResponse(t *testing.T) {
+	{
+		var res ApiResponse
+
+		jsonstr := `{"product_type": "HWE-SKT","product_name": "P1 Meter","serial": "3c39e7aabbcc","firmware_version": "2.11","api_version": "v1"}`
+		assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
+
+		assert.Equal(t, "HWE-SKT", res.ProductType)
+		assert.Equal(t, "v1", res.ApiVersion)
+	}
+}
+
+// Test StateResponse
+func TestUnmarshalStateResponse(t *testing.T) {
+	{
+		var res StateResponse
+
+		jsonstr := `{"power_on": true,"switch_lock": false,"brightness": 255}`
+		assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
+
+		assert.Equal(t, true, res.PowerOn)
+	}
+}
+
+// Test DataResponse
+func TestUnmarshalDataResponse(t *testing.T) {
+	{
+		var res DataResponse
+
+		jsonstr := `{"wifi_ssid": "My Wi-Fi","wifi_strength": 100,"total_power_import_t1_kwh": 30.511,"total_power_export_t1_kwh": 85.951,"active_power_w": 543,"active_power_l1_w": 676}`
+		assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
+
+		assert.Equal(t, float64(30.511), res.TotalPowerImportT1kWh+res.TotalPowerImportT2kWh+res.TotalPowerImportT3kWh+res.TotalPowerImportT4kWh)
+		assert.Equal(t, float64(543), res.ActivePowerW)
+	}
+}