Skip to content

Home Assistant: configurable charge status values (BC) - #32136

Merged
andig merged 4 commits into
masterfrom
feat/ha-status-values
Jul 25, 2026
Merged

Home Assistant: configurable charge status values (BC)#32136
andig merged 4 commits into
masterfrom
feat/ha-status-values

Conversation

@andig

@andig andig commented Jul 25, 2026

Copy link
Copy Markdown
Member

Replaces #32115.

Instead of growing the built-in Home Assistant charge status mapping with every vendor's wording, the mapping keeps only the states whose meaning is unambiguous and vendor-specific states become a per-device config option.

Built-in mapping (util/homeassistant):

Status States
A a, disconnected, not_plugged
B b, connected, plugged, starting, stopped, paused, complete, charging_completed
C c, charging

Removed (BC): on, true, active, 1, 2, 0, off, none, ready, initialising, preparing, no_power, notreadyforcharging. unknown/unavailable were dead entries since GetState already returns api.ErrNotAvailable for them.

New per-device option for charger and vehicle, comma-separated and case-insensitive. It extends the built-in mapping and overrides it only for the states it explicitly redefines:

type: homeassistant
status: sensor.porsche_charging_state
statusB: charging_stopped, charging_error
statusC: instant_charging

Templates get statusA/statusB/statusC as advanced params. Duplicate states across the three lists are rejected at config time, and an unmapped state now reports the raw value and entity so users know what to put into the lists.

One thing to decide: since on/off/true/false/1/0 are gone, a binary_sensor used as status entity now needs statusA: off + statusC: on. The charger template still offers binary_sensor in the status entity picker. Happy to keep on/off built-in if that is too sharp an edge.

ioBroker is not affected: its charger template renders a custom charger, so status goes through api.ChargeStatusString and only ever accepted A/B/C.

🤖 Generated with Claude Code

Drop the guessed vendor states from the built-in charge status mapping and
keep only the unambiguous ones (a/b/c, disconnected, connected, charging).
Vendor-specific states are now configured per device via statusA/statusB/
statusC as comma-separated, case-insensitive lists.
@andig andig added enhancement New feature or request devices Specific device support needs documentation Triggers issue creation in evcc-io/docs vehicles Specific vehicle support labels Jul 25, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="util/homeassistant/connection_test.go" line_range="49-55" />
<code_context>
 	}
 }

+func TestGetChargeStatus(t *testing.T) {
+	states, err := NewStatusMap("not_plugged", "Charging_Stopped, charging_error", "instant_charging")
+	require.NoError(t, err)
+
+	tests := []struct {
+		state string
+		want  api.ChargeStatus
+	}{
+		{"A", api.StatusA},
+		{"connected", api.StatusB},
+		{" charging ", api.StatusC},
+		{"not_plugged", api.StatusA},
+		{"CHARGING_STOPPED", api.StatusB},
+		{"charging_error", api.StatusB},
+		{"instant_charging", api.StatusC},
+		{"paused", api.StatusNone}, // no longer built-in
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.state, func(t *testing.T) {
+			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprintf(w, `{"entity_id":"sensor.foo","state":%q}`, tc.state)
+			}))
+			defer srv.Close()
+
+			status, err := newTestConnection(srv.URL).GetChargeStatus("sensor.foo", states)
+			assert.Equal(t, tc.want, status)
+			if tc.want == api.StatusNone {
</code_context>
<issue_to_address>
**suggestion (testing):** Add assertions on the error message content for unknown states to fully pin down the new behaviour

For the `paused` case we only assert that `err` is non-nil. Given `GetChargeStatus` now formats errors as `unknown charge status '%s' for entity %s`, please also assert that the error string includes the raw state (`paused`) and the entity ID. This will exercise the new diagnostics behaviour and protect against regressions in the error formatting.

```suggestion
			status, err := newTestConnection(srv.URL).GetChargeStatus("sensor.foo", states)
			assert.Equal(t, tc.want, status)
			if tc.want == api.StatusNone {
				assert.Error(t, err)

				// exercise diagnostics for unknown states (e.g. "paused")
				assert.Contains(t, err.Error(), tc.state)
				assert.Contains(t, err.Error(), "sensor.foo")
			} else {
				assert.NoError(t, err)
			}
```
</issue_to_address>

### Comment 2
<location path="util/homeassistant/connection_test.go" line_range="60-62" />
<code_context>
+	}
+}
+
+func TestNewStatusMapDuplicate(t *testing.T) {
+	_, err := NewStatusMap("foo", "foo", "")
+	assert.Error(t, err)
+}
+
</code_context>
<issue_to_address>
**suggestion (testing):** Extend NewStatusMap tests to cover duplicates within a single list and the happy-path mapping

Currently, duplicates are only tested across different lists. Please also add a case for duplicates within a single list (e.g. `NewStatusMap(

Suggested implementation:

```golang
func TestNewStatusMapDuplicate(t *testing.T) {
	t.Run("duplicate across lists", func(t *testing.T) {
		_, err := NewStatusMap("foo", "foo", "")
		assert.Error(t, err)
	})

	t.Run("duplicate within single list", func(t *testing.T) {
		_, err := NewStatusMap("foo,foo", "", "")
		assert.Error(t, err)
	})
}

func TestNewStatusMapMapping(t *testing.T) {
	statusMap, err := NewStatusMap("none", "charging", "disconnected")
	assert.NoError(t, err)

	assert.Equal(t, api.StatusNone, statusMap["none"])
	assert.Equal(t, api.StatusC, statusMap["charging"])
	assert.Equal(t, api.StatusB, statusMap["disconnected"])
}

```

1. Adjust the arguments to `NewStatusMap` in `TestNewStatusMapMapping` (`"none"`, `"charging"`, `"disconnected"`) to match the actual Home Assistant status strings used in your implementation.
2. Update the expected `api.Status*` values (`StatusNone`, `StatusC`, `StatusB`) so they reflect the real mapping that `NewStatusMap` is supposed to produce.
3. If `assert` from `testify` is not yet imported in this file, add `github.com/stretchr/testify/assert` to the import block; if you prefer using `require` for the `NoError` check, import `github.com/stretchr/testify/require` and swap `assert.NoError` for `require.NoError`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread util/homeassistant/connection_test.go Outdated
Comment thread util/homeassistant/connection_test.go
@andig
andig merged commit c8b2765 into master Jul 25, 2026
9 checks passed
@andig
andig deleted the feat/ha-status-values branch July 25, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devices Specific device support enhancement New feature or request needs documentation Triggers issue creation in evcc-io/docs vehicles Specific vehicle support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant