Repository: evcc-io/evcc (go)

Issue:
OpenEVSE: Invalid EVSE state
### Describe the bug

Error "charger: invalid EVSE state" is shown.

### Steps to reproduce

Use configuration below.
Chose a charging mode other than "Off".
Error is shown.



OpenEVSE version: 8.2.3.EU
OpenEVSE Wifi version: v4.2.2






### Configuration details

```yaml
chargers:
  - name: my_charger
    type: template
    template: openevse
    host: ****
    user: ****
    password: ****
```


### Log details

```text
[lp-1  ] INFO 2023/06/19 20:31:54 stop charging <-
[open***] TRACE 2023/06/19 20:31:54 GET http://****/override
[open***] TRACE 2023/06/19 20:31:54 {"state":"disabled","charge_current":6,"auto_release":true}
[open***] TRACE 2023/06/19 20:32:04 GET http://****/status
[open***] TRACE 2023/06/19 20:32:04 {"mode":"STA","wifi_client_connected":1,"eth_connected":0,"net_connected":1,"ipaddress":"****","emoncms_connected":0,"packets_sent":0,"packets_success":0,"mqtt_connected":1,"ocpp_connected":0,"rfid_failure":0,"ohm_hour":"NotConnected","free_heap":165964,"comm_sent":801489,"comm_success":801467,"rapi_connected":1,"***_connected":1,"amp":0,"voltage":230,"power":0,"pilot":6,"temp":520,"temp_max":520,"temp1":false,"temp2":false,"temp3":false,"temp4":520,"state":254,"status":"disabled","flags":1280,"vehicle":1,"colour":6,"manual_override":1,"freeram":165964,"divertmode":1,"srssi":-84,"time":"2023-06-19T18:32:05Z","local_time":"2023-06-19T20:32:05+0200","offset":"+0200","session_elapsed":636,"session_energy":0,"total_energy":0.034067667,"total_day":0,"total_week":0,"total_month":0,"total_year":0,"total_switches":239,"elapsed":636,"wattsec":0,"watthour":34.067667,"gfcicount":0,"nogndcount":1,"stuckcount":0,"solar":0,"grid_ie":0,"charge_rate":0,"divert_update":510111,"divert_active":false,"shaper":0,"shaper_live_pwr":0,"shaper_cur":0,"shaper_updated":false,"service_level":2,"limit":false,"ota_update":0,"config_version":2,"claims_version":5,"override_version":5,"schedule_version":1,"schedule_plan_version":1,"limit_version":1,"vehicle_state_update":510111,"tesla_vehicle_count":1,"tesla_vehicle_id":"****","tesla_vehicle_name":"****"}
```


### What type of operating system are you running?

HomeAssistant Add-on

### Version

0.118.1

Pull request:
OpenEVSE: fix status handling
Fixes #8542 

Patch:
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/i18n/da.toml b/i18n/da.toml
index 233fbaf3..731b3ad6 100644
--- a/i18n/da.toml
+++ b/i18n/da.toml
@@ -7,6 +7,16 @@ legendMiddleName = "Bil først"
 legendMiddleSubline = "Hjem efterfølgende"
 legendTitle = "Hvordan skal solenergi benyttes?"
 legendTopAutostart = "starter automatisk"
+legendTopName = "batterie understøtted opladning"
+legendTopSubline = "uden afbrydelser"
+modalTitle = "batterie indstillinger"
+note = "OBS: Disse indstillinger gælder kun for solar modus. Indstillingerne ændres tilsvarende."
+
+[batterySettings.bufferStart]
+full = "næsten fuld"
+low = "en smule fyld"
+medium = "halv fuld"
+never = "kun med nok solar overskuld"
 
 [footer]
 
@@ -23,8 +33,8 @@ powerSub2 = "opladning ..."
 tabTitle = "Live fællesskab"
 
 [footer.savings]
-footerLong = "{percent}% solenergi"
-footerShort = "{percent}% solenergi"
+footerLong = "{percent}% Fotovoltaisk energi"
+footerShort = "{percent}% Fotovoltaisk energi"
 modalTitle = "Oversigt over ladeenergi"
 percentGrid = "{grid} kWh Net"
 percentSelf = "{self} kWh solenergi"
@@ -83,9 +93,28 @@ blog = "Blog"
 docs = "Dokumentation"
 github = "GitHub"
 login = "Logins til køretøjer"
+needHelp = "Brug for hjælp?"
 sessions = "Opladning sessioner"
 settings = "Indstillinger"
 
+[help]
+discussionsButton = "GitHub diskussioner"
+documentationButton = "Dokumentation"
+issueButton = "Meld en fejl"
+issueDescription = "Fær en mærkelig eller forkert adfærd?"
+modalTitle = "Brug for hjælp?"
+primaryActions = "Noget fungerer ikke, som det skulle? Det er gode steder at få hjælp."
+restartButton = "Genstart"
+restartDescription = "Har du prøvet at slukke og tænde den igen?"
+secondaryActions = "Stadig ikke i stand til at løse dit problem? Her er nogle mere ekstreme muligheder."
+
+[help.restart]
+cancel = "Afbryd"
+confirm = "Ja, genstart!"
+description = "Under normale omstændigheder bør genstart ikke være nødvendig. Overvej at indsende en fejlmelding, hvis du har brug for at genstarte evcc regelmæssigt."
+disclaimer = "Bemærk: evcc afsluttes og har brug for at operativsystemet genstarter tjenesten."
+modalTitle = "Er du sikker på, at du vil genstarte?"
+
 [main]
 vehicles = "Parkering"
 
@@ -113,10 +142,14 @@ pvProduction = "Produktion"
 selfConsumption = "Self-konsumption"
 
 [main.loadpoint]
+avgPrice = "⌀ pris"
 charged = "Opladt"
+co2 = "⌀ CO₂"
 duration = "Varighed"
 fallbackName = "Ladepunkt"
 power = "Strøm"
+price = "Σ Pris"
+solar = "solenergi"
 
 [main.loadpointSettings]
 currents = "Ladestrøm"
@@ -209,6 +242,7 @@ vehicleTarget = "Køretøjsgrænse: {soc}%"
 charging = "Opladning..."
 cheapEnergyCharging = "Billig energi tilgængelig. Oplader..."
 cleanEnergyCharging = "Ren energi tilgængelig. Oplader..."
+climating = "Forkonditionering registreret."
 connected = "Forbundet."
 disconnected = "Afbrudt."
 guard = "Starter om {remaining}…"
@@ -234,21 +268,33 @@ reload = "Genindlæs?"
 
 [session]
 cancel = "Afbryd"
+co2 = "CO2"
+date = "Tidsrum"
 delete = "Slet"
 finished = "Fuldført"
-meterstart = "Meter start"
+meter = "Kilometertælller"
+meterstart = "kilometertælleraflæsning"
 meterstop = "Meter slut"
 odometer = "Kilometerstand"
+price = "Pris"
 started = "Startet"
 title = "Lade session"
 
 [sessions]
-date = "Periode"
+avgPrice = "⌀ pris"
+co2 = "⌀ CO₂"
+csvMonth = "Download {month} CSV"
+csvTotal = "Download total CSV"
+date = "Start"
 downloadCsv = "Download som CSV"
 energy = "Opladt"
 loadpoint = "Ladepunkt"
+noData = "Ingen opladninger i denne måned."
+price = "Σ pris"
 reallyDelete = "Er du sikker på at du vil slette denne session?"
+solar = "solenergi"
 title = "Opladning Sessioner"
+total = "Total"
 vehicle = "Køretøj"
 
 [sessions.csv]
@@ -262,6 +308,11 @@ meterstop = "Målerstop (kWh)"
 odometer = "Kilometertal (km)"
 vehicle = "Køretøj"
 
+[sessions.filter]
+allLoadpoints = "alle ladepunkter"
+allVehicles = "alle køretøjer"
+filter = "Filter"
+
 [settings]
 title = "Indstillinger"
 
@@ -290,7 +341,7 @@ light = "lys"
 [settings.unit]
 km = "km"
 label = "Enheder"
-mi = "miles"
+mi = "mil"
 
 [startupError]
 configFile = "Konfigurationsfil brugt:"
diff --git a/templates/definition/meter/enphase.yaml b/templates/definition/meter/enphase.yaml
index b4b832ab..2ff2f0e5 100644
--- a/templates/definition/meter/enphase.yaml
+++ b/templates/definition/meter/enphase.yaml
@@ -5,13 +5,15 @@ products:
       generic: IQ Envoy
 params:
   - name: usage
-    choice: ["grid", "pv"]
+    choice: ["grid", "pv", "battery"]
   - name: host
     required: true
   - name: token
     help:
       en: "Required if Envoy Firmware D7.x.xxx. Token is valid for one year. Instructions for obtaining a token via web UI: https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication"
       de: "Ab Envoy Firmware D7.x.xxx notwendig. Token ist ein Jahr gültig. Anleitung (Obtaining a token via web UI): https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication"
+  - name: capacity
+    advanced: true
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -26,7 +28,6 @@ render: |
     insecure: true
     {{- end }}
     jq: .consumption[] | select(.measurementType == "net-consumption").wNow
-    scale: 1
   energy:
     source: http
     uri: http://{{ .host }}/production.json
@@ -52,7 +53,6 @@ render: |
     insecure: true
     {{- end }}
     jq: .production[] | select(.measurementType == "production").wNow
-    scale: 1
   energy:
     source: http
     uri: http://{{ .host }}/production.json
@@ -66,3 +66,30 @@ render: |
     jq: .production[] | select(.measurementType == "production").whLifetime
     scale: 0.001
   {{- end }}
+  {{- if eq .usage "battery" }}
+  power:
+    source: http
+    uri: http://{{ .host }}/production.json?details=1
+    method: GET
+    {{- if .token }}
+    auth:
+      type: bearer
+      password: {{ .token }}
+    insecure: true
+    {{- end }}
+    jq: .storage[] | select(.type == "acb").wNow
+  soc:
+    source: http
+    uri: http://{{ .host }}/ivp/ensemble/inventory
+    method: GET
+    {{- if .token }}
+    auth:
+      type: bearer
+      password: {{ .token }}
+    insecure: true
+    {{- end }}
+    jq: .[].devices | map(.percentFull) | add / length
+  {{- if .capacity }}
+  capacity: {{ .capacity }} # kWh
+  {{- end }}
+  {{- end }}
diff --git a/templates/definition/meter/youless.yaml b/templates/definition/meter/youless.yaml
index 68d076b5..22f39cd1 100644
--- a/templates/definition/meter/youless.yaml
+++ b/templates/definition/meter/youless.yaml
@@ -3,13 +3,20 @@ products:
   - brand: Youless
     description:
       generic: Energy Monitor
+requirements:
+  description:
+    de: |
+      Zur Erfassung der PV-Produktion wird ein extern angebundenener S0-Erzeugungszähler benötigt.
+    en: |
+      An externally connected S0 generation meter is required to record the solar production.
 params:
   - name: usage
-    choice: ["grid"]
+    choice: ["grid", "pv"]
   - name: host
     required: true
 render: |
   type: custom
+  {{- if eq .usage "grid" }}
   power:
     source: http
     uri: http://{{ .host }}/e
@@ -20,3 +27,16 @@ render: |
     uri: http://{{ .host }}/e
     method: GET
     jq: .[0]|.p1+.p2
+  {{- end }}
+  {{- if eq .usage "pv" }}
+  power:
+    source: http
+    uri: http://{{ .host }}/e
+    method: GET
+    jq: .[0].ps0
+  energy:
+    source: http
+    uri: http://{{ .host }}/e
+    method: GET
+    jq: .[0].cs0
+  {{- end }}
diff --git a/templates/docs/meter/enphase_0.yaml b/templates/docs/meter/enphase_0.yaml
index 09000212..9d2ee2ca 100644
--- a/templates/docs/meter/enphase_0.yaml
+++ b/templates/docs/meter/enphase_0.yaml
@@ -9,6 +9,12 @@ render:
       usage: grid
       host: 192.0.2.2 # IP-Adresse oder Hostname
       token: # Ab Envoy Firmware D7.x.xxx notwendig. Token ist ein Jahr gültig. Anleitung (Obtaining a token via web UI): https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication (Optional)
+    advanced: |
+      type: template
+      template: enphase
+      usage: grid
+      host: 192.0.2.2 # IP-Adresse oder Hostname
+      token: # Ab Envoy Firmware D7.x.xxx notwendig. Token ist ein Jahr gültig. Anleitung (Obtaining a token via web UI): https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication (Optional)
   - usage: pv
     default: |
       type: template
@@ -16,3 +22,23 @@ render:
       usage: pv
       host: 192.0.2.2 # IP-Adresse oder Hostname
       token: # Ab Envoy Firmware D7.x.xxx notwendig. Token ist ein Jahr gültig. Anleitung (Obtaining a token via web UI): https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication (Optional)
+    advanced: |
+      type: template
+      template: enphase
+      usage: pv
+      host: 192.0.2.2 # IP-Adresse oder Hostname
+      token: # Ab Envoy Firmware D7.x.xxx notwendig. Token ist ein Jahr gültig. Anleitung (Obtaining a token via web UI): https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication (Optional)
+  - usage: battery
+    default: |
+      type: template
+      template: enphase
+      usage: battery
+      host: 192.0.2.2 # IP-Adresse oder Hostname
+      token: # Ab Envoy Firmware D7.x.xxx notwendig. Token ist ein Jahr gültig. Anleitung (Obtaining a token via web UI): https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication (Optional)
+    advanced: |
+      type: template
+      template: enphase
+      usage: battery
+      host: 192.0.2.2 # IP-Adresse oder Hostname
+      token: # Ab Envoy Firmware D7.x.xxx notwendig. Token ist ein Jahr gültig. Anleitung (Obtaining a token via web UI): https://enphase.com/download/accessing-iq-gateway-local-apis-or-local-ui-token-based-authentication (Optional)
+      capacity: 50 # Akkukapazität in kWh (Optional)
diff --git a/templates/docs/meter/youless_0.yaml b/templates/docs/meter/youless_0.yaml
index ee9f7460..7753a7cd 100644
--- a/templates/docs/meter/youless_0.yaml
+++ b/templates/docs/meter/youless_0.yaml
@@ -1,6 +1,9 @@
 product:
   brand: Youless
   description: Energy Monitor
+description: |
+  Zur Erfassung der PV-Produktion wird ein extern angebundenener S0-Erzeugungszähler benötigt.
+
 render:
   - usage: grid
     default: |
@@ -8,3 +11,9 @@ render:
       template: youless
       usage: grid
       host: 192.0.2.2 # IP-Adresse oder Hostname
+  - usage: pv
+    default: |
+      type: template
+      template: youless
+      usage: pv
+      host: 192.0.2.2 # IP-Adresse oder Hostname
diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json
index ed42d065..e2d5b021 100644
--- a/templates/evcc.io/brands.json
+++ b/templates/evcc.io/brands.json
@@ -174,6 +174,7 @@
   "TP-Link",
   "VARTA",
   "Victron",
+  "Youless",
   "ZCS Azzurro"
  ],
  "Vehicles": [
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")
+	// }
 }