Repository: evcc-io/evcc
Pull request #9036 (2023-07-20 10:07:54)

Clarification of Elli wallboxes are supported best
The current Elli wallboxes contain lots of software bugs, which make their usage and also support a nightmare.

Only the following two scenarios provide a somehow stable environment which can be “recommended”:

- Elli Pro: No CT coil usage, LAN connection. If a LAN connection is not possible, addition of an external meter
- Elli Pro: No CT coil usage, external meter usage.

A LAN connection is always highly recommended, as even the slightest network connection drop will allow charging of the car with minimal power until reconnection is establied.

There is no workaround for the wallbox software crashing often on connection attempts which can take a few minutes for the connection to work. During that time the EV is allowed to charge with minimal power.

StefanSchoof: Should there also a mention, that the car should not connected when (re)starting evcc?

Patch:
diff --git a/charger/abl-em4.go b/charger/abl-em4.go
new file mode 100644
index 00000000..168fcd73
--- /dev/null
+++ b/charger/abl-em4.go
@@ -0,0 +1,233 @@
+package charger
+
+// LICENSE
+
+// Copyright (c) 2023 premultiply
+
+// This module is NOT covered by the MIT license. All rights reserved.
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import (
+	"encoding/binary"
+	"fmt"
+
+	"github.com/evcc-io/evcc/api"
+	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/modbus"
+	"github.com/evcc-io/evcc/util/sponsor"
+)
+
+const (
+	// per-unit registers
+	abl4Offset = 0x100
+
+	abl4RegCurrents   = 0x3001 // 0.1A (3x)
+	abl4RegVoltages   = 0x3007 // 0.1V (3x)
+	abl4RegPower      = 0x300d // 1W
+	abl4RegEnergy     = 0x300f // 0.01kWh
+	abl4RegStatus     = 0x3031
+	abl4RegMaxCurrent = 0x3032 // 0.1A
+)
+
+// AblEm4 is an api.Charger implementation for ABL eM4 controller
+type AblEm4 struct {
+	log     *util.Logger
+	conn    *modbus.Connection
+	base    uint16
+	current uint16
+}
+
+func init() {
+	registry.Add("abl-em4", NewAblEm4FromConfig)
+}
+
+// NewAblEm4FromConfig creates an ABL eM4 charger from generic config
+func NewAblEm4FromConfig(other map[string]interface{}) (api.Charger, error) {
+	cc := struct {
+		modbus.TcpSettings `mapstructure:",squash"`
+		Connector          uint16
+	}{
+		TcpSettings: modbus.TcpSettings{
+			ID: 255, // default
+		},
+		Connector: 1,
+	}
+
+	if err := util.DecodeOther(other, &cc); err != nil {
+		return nil, err
+	}
+
+	return NewAblEm4(cc.URI, cc.ID, cc.Connector)
+}
+
+// NewAblEm4 creates an ABL eM4 charger
+func NewAblEm4(uri string, id uint8, connector uint16) (*AblEm4, error) {
+	conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, id)
+	if err != nil {
+		return nil, err
+	}
+
+	if !sponsor.IsAuthorized() {
+		return nil, api.ErrSponsorRequired
+	}
+
+	log := util.NewLogger("abl-em4")
+	conn.Logger(log.TRACE)
+
+	wb := &AblEm4{
+		log:     log,
+		conn:    conn,
+		current: 60, // assume min current
+	}
+
+	wb.base = (connector - 1) * abl4Offset
+
+	// get initial state from charger
+	curr, err := wb.getCurrent()
+	if err != nil {
+		return nil, fmt.Errorf("current limit: %w", err)
+	}
+	if curr > 0 {
+		wb.current = curr
+	}
+
+	return wb, nil
+}
+
+func (wb *AblEm4) setCurrent(current uint16) error {
+	b := make([]byte, 2)
+	binary.BigEndian.PutUint16(b, current)
+
+	_, err := wb.conn.WriteMultipleRegisters(wb.base+abl4RegMaxCurrent, 1, b)
+
+	return err
+}
+
+func (wb *AblEm4) getCurrent() (uint16, error) {
+	b, err := wb.conn.ReadHoldingRegisters(wb.base+abl4RegMaxCurrent, 1)
+	if err != nil {
+		return 0, err
+	}
+
+	return binary.BigEndian.Uint16(b), nil
+}
+
+// Status implements the api.Charger interface
+func (wb *AblEm4) Status() (api.ChargeStatus, error) {
+	b, err := wb.conn.ReadHoldingRegisters(wb.base+abl4RegStatus, 1)
+	if err != nil {
+		return api.StatusNone, err
+	}
+
+	r := rune(b[1]>>4-0x0A) + 'A'
+
+	switch r {
+	case 'A', 'B', 'C':
+		return api.ChargeStatus(r), nil
+	default:
+		status, ok := ablStatus[b[1]]
+		if !ok {
+			status = string(r)
+		}
+
+		return api.StatusNone, fmt.Errorf("invalid status: %s", status)
+	}
+}
+
+// Enabled implements the api.Charger interface
+func (wb *AblEm4) Enabled() (bool, error) {
+	curr, err := wb.getCurrent()
+
+	return curr > 0, err
+}
+
+// Enable implements the api.Charger interface
+func (wb *AblEm4) Enable(enable bool) error {
+	var current uint16
+	if enable {
+		current = wb.current
+	}
+
+	return wb.setCurrent(current)
+}
+
+// MaxCurrent implements the api.Charger interface
+func (wb *AblEm4) MaxCurrent(current int64) error {
+	return wb.MaxCurrentMillis(float64(current))
+}
+
+var _ api.ChargerEx = (*AblEm4)(nil)
+
+// MaxCurrentMillis implements the api.ChargerEx interface
+func (wb *AblEm4) MaxCurrentMillis(current float64) error {
+	if current < 6 {
+		return fmt.Errorf("invalid current %.1f", current)
+	}
+
+	wb.current = uint16(current * 10)
+
+	return wb.setCurrent(wb.current)
+}
+
+var _ api.Meter = (*AblEm4)(nil)
+
+// currentPower implements the api.Meter interface
+func (wb *AblEm4) CurrentPower() (float64, error) {
+	b, err := wb.conn.ReadHoldingRegisters(wb.base+abl4RegPower, 2)
+	if err != nil {
+		return 0, err
+	}
+
+	return float64(binary.BigEndian.Uint32(b)), nil
+}
+
+var _ api.MeterEnergy = (*AblEm4)(nil)
+
+// totalEnergy implements the api.MeterEnergy interface
+func (wb *AblEm4) TotalEnergy() (float64, error) {
+	b, err := wb.conn.ReadHoldingRegisters(wb.base+abl4RegEnergy, 2)
+	if err != nil {
+		return 0, err
+	}
+
+	return float64(binary.BigEndian.Uint32(b)) * 0.01, nil
+}
+
+// getPhaseValues returns 3 sequential register values
+func (wb *AblEm4) getPhaseValues(reg uint16) (float64, float64, float64, error) {
+	b, err := wb.conn.ReadHoldingRegisters(reg, 3*2)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	var res [3]float64
+	for i := 0; i < 3; i++ {
+		res[i] = float64(binary.BigEndian.Uint32(b[4*i:])) * 0.1
+	}
+
+	return res[0], res[1], res[2], nil
+}
+
+var _ api.PhaseCurrents = (*AblEm4)(nil)
+
+// Currents implements the api.PhaseCurrents interface
+func (wb *AblEm4) Currents() (float64, float64, float64, error) {
+	return wb.getPhaseValues(wb.base + abl4RegCurrents)
+}
+
+var _ api.PhaseVoltages = (*AblEm4)(nil)
+
+// Voltages implements the api.PhaseVoltages interface
+func (wb *AblEm4) Voltages() (float64, float64, float64, error) {
+	return wb.getPhaseValues(wb.base + abl4RegVoltages)
+}
diff --git a/charger/easee.go b/charger/easee.go
index 317e3aff..344355c7 100644
--- a/charger/easee.go
+++ b/charger/easee.go
@@ -57,10 +57,11 @@ type Easee struct {
 	phaseMode             int
 	currentPower, sessionEnergy, totalEnergy,
 	currentL1, currentL2, currentL3 float64
-	rfid string
-	lp   loadpoint.API
-	cmdC chan easee.SignalRCommandResponse
-	obsC chan easee.Observation
+	rfid    string
+	lp      loadpoint.API
+	cmdC    chan easee.SignalRCommandResponse
+	obsC    chan easee.Observation
+	obsTime map[easee.ObservationID]time.Time
 }
 
 func init() {
@@ -105,6 +106,7 @@ func NewEasee(user, password, charger string, timeout time.Duration) (*Easee, er
 		done:    make(chan struct{}),
 		cmdC:    make(chan easee.SignalRCommandResponse),
 		obsC:    make(chan easee.Observation),
+		obsTime: make(map[easee.ObservationID]time.Time),
 	}
 
 	c.Client.Timeout = timeout
@@ -282,6 +284,12 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
 	}
 	c.updated = time.Now()
 
+	if prevTime, ok := c.obsTime[res.ID]; ok && prevTime.After(res.Timestamp) {
+		// received observation is outdated, ignoring
+		return
+	}
+	c.obsTime[res.ID] = res.Timestamp
+
 	switch res.ID {
 	case easee.USER_IDTOKEN:
 		c.rfid = res.Value
diff --git a/charger/openevse.go b/charger/openevse.go
index 30758621..2b4cdb4a 100644
--- a/charger/openevse.go
+++ b/charger/openevse.go
@@ -180,24 +180,27 @@ func (c *OpenEVSE) Status() (api.ChargeStatus, error) {
 func (c *OpenEVSE) Enabled() (bool, error) {
 	ctx, cancel := c.requestContextWithTimeout()
 	defer cancel()
-	overrideResp, err := c.api.GetManualOverrideWithResponse(ctx)
+	overrideResp, err := c.api.GetStatusWithResponse(ctx)
 
 	if err != nil {
 		return false, err
 	}
 
 	if overrideResp.JSON200 != nil && overrideResp.JSON200.State != nil {
-		switch state := *overrideResp.JSON200.State; state {
+		status := *overrideResp.JSON200.Status
+
+		switch status {
 		case "disabled":
 			return false, nil
 		case "enabled", "active":
 			return true, nil
 		default:
-			return false, fmt.Errorf("unknown EVSE state: %s", state)
+			return false, fmt.Errorf("unknown EVSE status: %s", status)
 		}
 	}
 
-	return false, errors.New("invalid EVSE state")
+	// no override:
+	return false, fmt.Errorf("invalid EVSE status")
 }
 
 // Enable implements the api.Charger interface
diff --git a/charger/openevse/api.go b/charger/openevse/api.go
index 0c10fe7f..08414cf2 100644
--- a/charger/openevse/api.go
+++ b/charger/openevse/api.go
@@ -2087,9 +2087,12 @@ type GetStatusResponse struct {
 		// WiFi RSSI signal strength
 		Srssi *int `json:"srssi,omitempty"`
 
-		// The EVSE status
+		// The EVSE state
 		State *int `json:"state,omitempty"`
 
+		// The EVSE status
+		Status *string `json:"status,omitempty"`
+
 		// Number of stuck relay errors
 		Stuckcount *int `json:"stuckcount,omitempty"`
 
@@ -3265,9 +3268,12 @@ func ParseGetStatusResponse(rsp *http.Response) (*GetStatusResponse, error) {
 			// WiFi RSSI signal strength
 			Srssi *int `json:"srssi,omitempty"`
 
-			// The EVSE status
+			// The EVSE state
 			State *int `json:"state,omitempty"`
 
+			// The EVSE status
+			Status *string `json:"status,omitempty"`
+
 			// Number of stuck relay errors
 			Stuckcount *int `json:"stuckcount,omitempty"`
 
diff --git a/core/site.go b/core/site.go
index 05f22c6a..69e30304 100644
--- a/core/site.go
+++ b/core/site.go
@@ -33,12 +33,14 @@ type Updater interface {
 
 // meterMeasurement is used as slice element for publishing structured data
 type meterMeasurement struct {
-	Power float64 `json:"power"`
+	Power  float64 `json:"power"`
+	Energy float64 `json:"energy"`
 }
 
 // batteryMeasurement is used as slice element for publishing structured data
 type batteryMeasurement struct {
 	Power    float64 `json:"power"`
+	Energy   float64 `json:"energy"`
 	Soc      float64 `json:"soc"`
 	Capacity float64 `json:"capacity"`
 }
@@ -394,15 +396,17 @@ func (site *Site) updateMeters() error {
 	}
 
 	if len(site.pvMeters) > 0 {
+		var totalEnergy float64
+
 		site.pvPower = 0
+
 		mm := make([]meterMeasurement, len(site.pvMeters))
 
 		for i, meter := range site.pvMeters {
+			// pv power
 			var power float64
 			err := retry.Do(site.updateMeter(meter, &power), retryOptions...)
 
-			mm[i] = meterMeasurement{Power: power}
-
 			if err == nil {
 				// ignore negative values which represent self-consumption
 				site.pvPower += math.Max(0, power)
@@ -413,22 +417,43 @@ func (site *Site) updateMeters() error {
 				err = fmt.Errorf("pv %d power: %v", i+1, err)
 				site.log.ERROR.Println(err)
 			}
+
+			// pv energy (production)
+			var energy float64
+			if m, ok := meter.(api.MeterEnergy); err == nil && ok {
+				energy, err = m.TotalEnergy()
+				if err == nil {
+					totalEnergy += energy
+				} else {
+					site.log.ERROR.Printf("pv %d energy: %v", i+1, err)
+				}
+			}
+
+			mm[i] = meterMeasurement{
+				Power:  power,
+				Energy: energy,
+			}
 		}
 
 		site.log.DEBUG.Printf("pv power: %.0fW", site.pvPower)
 		site.publish("pvPower", site.pvPower)
 
+		site.publish("pvEnergy", totalEnergy)
+
 		site.publish("pv", mm)
 	}
 
 	if len(site.batteryMeters) > 0 {
 		var totalCapacity float64
+		var totalEnergy float64
+
 		site.batteryPower = 0
 		site.batterySoc = 0
 
 		mm := make([]batteryMeasurement, len(site.batteryMeters))
 
 		for i, meter := range site.batteryMeters {
+			// battery power
 			var power float64
 
 			// NOTE battery errors are logged but ignored as we don't consider them relevant
@@ -443,6 +468,18 @@ func (site *Site) updateMeters() error {
 				site.log.ERROR.Printf("battery %d power: %v", i+1, err)
 			}
 
+			// battery energy (discharge)
+			var energy float64
+			if m, ok := meter.(api.MeterEnergy); err == nil && ok {
+				energy, err = m.TotalEnergy()
+				if err == nil {
+					totalEnergy += energy
+				} else {
+					site.log.ERROR.Printf("battery %d energy: %v", i+1, err)
+				}
+			}
+
+			// battery soc and capacity
 			var capacity float64
 			soc, err := meter.(api.Battery).Soc()
 
@@ -465,6 +502,7 @@ func (site *Site) updateMeters() error {
 
 			mm[i] = batteryMeasurement{
 				Power:    power,
+				Energy:   energy,
 				Soc:      soc,
 				Capacity: capacity,
 			}
@@ -484,12 +522,15 @@ func (site *Site) updateMeters() error {
 		site.log.DEBUG.Printf("battery power: %.0fW", site.batteryPower)
 		site.publish("batteryPower", site.batteryPower)
 
+		site.publish("batteryEnergy", totalEnergy)
+
 		site.publish("battery", mm)
 	}
 
+	// grid power
 	err := retryMeter("grid", site.gridMeter, &site.gridPower)
 
-	// powers
+	// grid phase powers
 	var p1, p2, p3 float64
 	if phaseMeter, ok := site.gridMeter.(api.PhasePowers); err == nil && ok {
 		p1, p2, p3, err = phaseMeter.Powers()
@@ -502,7 +543,7 @@ func (site *Site) updateMeters() error {
 		}
 	}
 
-	// currents
+	// grid phase currents (signed)
 	if phaseMeter, ok := site.gridMeter.(api.PhaseCurrents); err == nil && ok {
 		var i1, i2, i3 float64
 		i1, i2, i3, err = phaseMeter.Currents()
@@ -515,11 +556,12 @@ func (site *Site) updateMeters() error {
 		}
 	}
 
-	// energy
+	// grid energy (import)
 	if energyMeter, ok := site.gridMeter.(api.MeterEnergy); err == nil && ok {
-		val, err := energyMeter.TotalEnergy()
+		var e float64
+		e, err = energyMeter.TotalEnergy()
 		if err == nil {
-			site.publish("gridEnergy", val)
+			site.publish("gridEnergy", e)
 		} else {
 			site.log.ERROR.Printf("grid energy: %v", err)
 		}
diff --git a/meter/tapo/connection.go b/meter/tapo/connection.go
index ef81c4cc..6a635b9d 100644
--- a/meter/tapo/connection.go
+++ b/meter/tapo/connection.go
@@ -104,6 +104,10 @@ func (d *Connection) Login() error {
 		return err
 	}
 
+	if err := d.CheckErrorCode(res.ErrorCode); err != nil {
+		return err
+	}
+
 	d.Token = res.Result.Token
 
 	deviceResponse, err := d.ExecMethod("get_device_info", false)
diff --git a/templates/definition/charger/abl-em4.yaml b/templates/definition/charger/abl-em4.yaml
new file mode 100644
index 00000000..3e599287
--- /dev/null
+++ b/templates/definition/charger/abl-em4.yaml
@@ -0,0 +1,18 @@
+template: abl-em4
+products:
+  - brand: ABL
+    description:
+      generic: eM4 (SBCx)
+capabilities: ["mA"]
+requirements:
+  evcc: ["sponsorship"]
+params:
+  - name: modbus
+    choice: ["tcpip"]
+    id: 255
+  - name: connector
+    default: 1
+render: |
+  type: abl-em4
+  {{- include "modbus" . }}
+  connector: {{ .connector }}
diff --git a/templates/definition/charger/elli-charger-connect.yaml b/templates/definition/charger/elli-charger-connect.yaml
index c8e7a6a0..3ee39124 100644
--- a/templates/definition/charger/elli-charger-connect.yaml
+++ b/templates/definition/charger/elli-charger-connect.yaml
@@ -24,7 +24,7 @@ requirements:
 
       Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-      Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+      Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
       Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
     en: |
@@ -32,7 +32,7 @@ requirements:
 
       The identification of a vehicle using the RFID card is not possible.
 
-      The support is in beta state and problems can occur!
+      Important: A mostly flawless functionality can only be provided with an external energy meter and no usage of CT coils, due to sosftware bugs of the Wallbox. Using a LAN connection is highly recommended.
 
       Note: If you've added an energy meter to your charger please use the Pro or Connected+ integration.
 params:
diff --git a/templates/definition/charger/elli-charger-pro.yaml b/templates/definition/charger/elli-charger-pro.yaml
index a6f11b7d..d89ff782 100644
--- a/templates/definition/charger/elli-charger-pro.yaml
+++ b/templates/definition/charger/elli-charger-pro.yaml
@@ -24,13 +24,13 @@ requirements:
 
       Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-      Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+      Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
     en: |
       The device has to have a fix IP address (manuall or via DHCP).
 
       The identification of a vehicle using the RFID card is not possible.
 
-      The support is in beta state and problems can occur!
+      Important: A mostly flawless functionality can only be provided with an external energy meter and no usage of CT coils, due to sosftware bugs of the Wallbox.  Using a LAN connection is highly recommended.
 params:
   - preset: eebus
   - name: ip
diff --git a/templates/docs/charger/abl-em4_0.yaml b/templates/docs/charger/abl-em4_0.yaml
new file mode 100644
index 00000000..c5d06051
--- /dev/null
+++ b/templates/docs/charger/abl-em4_0.yaml
@@ -0,0 +1,25 @@
+product:
+  brand: ABL
+  description: eM4 (SBCx)
+capabilities: ["mA"]
+requirements: ["sponsorship"]
+render:
+  - default: |
+      type: template
+      template: abl-em4
+
+      # Modbus TCP
+      modbus: tcpip
+      id: 255
+      host: 192.0.2.2 # Hostname
+      port: 502 # Port
+    advanced: |
+      type: template
+      template: abl-em4
+
+      # Modbus TCP
+      modbus: tcpip
+      id: 255
+      host: 192.0.2.2 # Hostname
+      port: 502 # Port
+      connector: 1 # Optional
diff --git a/templates/docs/charger/elliconnect_0.yaml b/templates/docs/charger/elliconnect_0.yaml
index 0db51911..b70886cf 100644
--- a/templates/docs/charger/elliconnect_0.yaml
+++ b/templates/docs/charger/elliconnect_0.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
   Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
 
diff --git a/templates/docs/charger/elliconnect_1.yaml b/templates/docs/charger/elliconnect_1.yaml
index f7f2619a..0425e676 100644
--- a/templates/docs/charger/elliconnect_1.yaml
+++ b/templates/docs/charger/elliconnect_1.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
   Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
 
diff --git a/templates/docs/charger/elliconnect_2.yaml b/templates/docs/charger/elliconnect_2.yaml
index 3dcb1a7f..e3bf17ae 100644
--- a/templates/docs/charger/elliconnect_2.yaml
+++ b/templates/docs/charger/elliconnect_2.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
   Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
 
diff --git a/templates/docs/charger/elliconnect_3.yaml b/templates/docs/charger/elliconnect_3.yaml
index c01d558c..97bc8777 100644
--- a/templates/docs/charger/elliconnect_3.yaml
+++ b/templates/docs/charger/elliconnect_3.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
   Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
 
diff --git a/templates/docs/charger/elliconnect_4.yaml b/templates/docs/charger/elliconnect_4.yaml
index 070abde3..55de1c05 100644
--- a/templates/docs/charger/elliconnect_4.yaml
+++ b/templates/docs/charger/elliconnect_4.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
   Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
 
diff --git a/templates/docs/charger/ellipro_0.yaml b/templates/docs/charger/ellipro_0.yaml
index 196da10e..62ead704 100644
--- a/templates/docs/charger/ellipro_0.yaml
+++ b/templates/docs/charger/ellipro_0.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
 render:
   - default: |
diff --git a/templates/docs/charger/ellipro_1.yaml b/templates/docs/charger/ellipro_1.yaml
index 951700fa..b6e087bd 100644
--- a/templates/docs/charger/ellipro_1.yaml
+++ b/templates/docs/charger/ellipro_1.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
 render:
   - default: |
diff --git a/templates/docs/charger/ellipro_2.yaml b/templates/docs/charger/ellipro_2.yaml
index c5ad01e8..c32d2765 100644
--- a/templates/docs/charger/ellipro_2.yaml
+++ b/templates/docs/charger/ellipro_2.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
 render:
   - default: |
diff --git a/templates/docs/charger/ellipro_3.yaml b/templates/docs/charger/ellipro_3.yaml
index 3167c13f..d28bd3c7 100644
--- a/templates/docs/charger/ellipro_3.yaml
+++ b/templates/docs/charger/ellipro_3.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
 render:
   - default: |
diff --git a/templates/docs/charger/ellipro_4.yaml b/templates/docs/charger/ellipro_4.yaml
index 9c1abcfb..c383bcd7 100644
--- a/templates/docs/charger/ellipro_4.yaml
+++ b/templates/docs/charger/ellipro_4.yaml
@@ -8,7 +8,7 @@ description: |
 
   Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
-  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+  Wichtig: Die möglichst reibungslose Funktionalität ist aufgrund von Software-Fehlern in der Wallbox nur mit einem externen Energiezähler und ohne Stromwandlerspulen möglich! Eine LAN Anbindung wird sehr empfohlen.
 
 render:
   - default: |
diff --git a/vehicle/niu/types.go b/vehicle/niu/types.go
index fbe55532..7d4c2007 100644
--- a/vehicle/niu/types.go
+++ b/vehicle/niu/types.go
@@ -51,7 +51,7 @@ type Response struct {
 				BatteryCharging int64 `json:"batteryCharging,omitempty"`
 			} `json:"compartmentA"`
 		}
-		LeftTime         string `json:"leftTime,omitempty"`
-		EstimatedMileage int64  `json:"estimatedMileage,omitempty"`
+		// LeftTime         float32 `json:"leftTime,omitempty"`
+		EstimatedMileage int64 `json:"estimatedMileage,omitempty"`
 	}
 }


Test patch:
diff --git a/vehicle/niu/types_test.go b/vehicle/niu/types_test.go
index e37768b2..3c4c39f3 100644
--- a/vehicle/niu/types_test.go
+++ b/vehicle/niu/types_test.go
@@ -28,7 +28,7 @@ func TestUnmarshalNiuToken(t *testing.T) {
 
 func TestUnmarshalNiuResponse(t *testing.T) {
 	var tok Response
-	str := `{"data":{"isCharging":0,"lockStatus":0,"isAccOn":0,"isFortificationOn":0,"isConnected":true,"position":{"lat":42.330353,"lng":11.450598},"hdop":1,"time":1617697890173,"batteries":{"compartmentA":{"bmsId":"bmsId","isConnected":true,"batteryCharging":59,"gradeBattery":"94.8"},"compartmentB":{"bmsId":"bmsId","isConnected":true,"batteryCharging":58,"gradeBattery":"95.9"}},"leftTime":"10.2","estimatedMileage":68,"gpsTimestamp":1617697890173,"infoTimestamp":1617697890173,"nowSpeed":0,"shakingValue":3,"locationType":1,"batteryDetail":true,"centreCtrlBattery":100,"ss_protocol_ver":3,"ss_online_sta":"1","gps":5,"gsm":14,"lastTrack":{"ridingTime":90,"distance":571,"time":1617269526360}},"desc":"成功","trace":"成功","status":0}`
+	str := `{"data":{"isCharging":0,"lockStatus":0,"isAccOn":0,"isFortificationOn":0,"isConnected":true,"position":{"lat":42.330353,"lng":11.450598},"hdop":1,"time":1617697890173,"batteries":{"compartmentA":{"bmsId":"bmsId","isConnected":true,"batteryCharging":59,"gradeBattery":"94.8"},"compartmentB":{"bmsId":"bmsId","isConnected":true,"batteryCharging":58,"gradeBattery":"95.9"}},"leftTime":10.2,"estimatedMileage":68,"gpsTimestamp":1617697890173,"infoTimestamp":1617697890173,"nowSpeed":0,"shakingValue":3,"locationType":1,"batteryDetail":true,"centreCtrlBattery":100,"ss_protocol_ver":3,"ss_online_sta":"1","gps":5,"gsm":14,"lastTrack":{"ridingTime":90,"distance":571,"time":1617269526360}},"desc":"成功","trace":"成功","status":0}`
 
 	if err := json.Unmarshal([]byte(str), &tok); err != nil {
 		t.Error(err)
@@ -46,7 +46,7 @@ func TestUnmarshalNiuResponse(t *testing.T) {
 		t.Error("IsCharging")
 	}
 
-	if tok.Data.LeftTime != "10.2" {
-		t.Error("LeftTime")
-	}
+	// if tok.Data.LeftTime != 10.2 {
+	// 	t.Error("LeftTime")
+	// }
 }