Config: show vehicle asleep state instead of value errors (#31859)

This commit is contained in:
Michael Geers 2026-07-17 14:38:59 +02:00 • committed by GitHub
parent 0cf708a40d
commit eed3aece14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 83 additions and 7 deletions

View file

@ -100,16 +100,16 @@ export default {
!PHASE_TAGS.includes(name) &&
!FORECAST_TAGS.includes(name)
)
.map(([name, { value, error, warning, muted }]) => {
return { name, value, error, warning, muted };
.map(([name, { value, error, warning, muted, asleep }]) => {
return { name, value, error, warning, muted, asleep };
});
},
phaseEntries() {
return Object.entries(this.tags)
.filter(([name]) => PHASE_TAGS.includes(name))
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, { value, error, warning, muted }]) => {
return { name, value, error, warning, muted };
.map(([name, { value, error, warning, muted, asleep }]) => {
return { name, value, error, warning, muted, asleep };
});
},
hasPhaseEntries() {
@ -182,6 +182,9 @@ export default {
: "text-nowrap flex-shrink-0";
},
valueClasses(entry) {
if (entry.asleep) {
return "value--muted";
}
if (entry.error) {
return "value--error";
}
@ -195,6 +198,9 @@ export default {
},
fmtDeviceValue(entry) {
const { name, value } = entry;
if (entry.asleep) {
return this.$t("config.deviceValue.asleep");
}
if (value === null || value === undefined) {
return "";
}

View file

@ -188,6 +188,7 @@
"deviceValue": {
"activeClients": "Aktive Clients",
"amount": "Anzahl",
"asleep": "schläft",
"broker": "Broker",
"bucket": "Bucket",
"capacity": "Kapazität",

View file

@ -188,6 +188,7 @@
"deviceValue": {
"activeClients": "Active clients",
"amount": "Amount",
"asleep": "sleeping",
"broker": "Broker",
"bucket": "Bucket",
"capacity": "Capacity",

View file

@ -7,6 +7,7 @@ import (
)
type errorPlugin struct {
*getter
err error
}
@ -32,10 +33,19 @@ func NewErrorFromConfig(other map[string]any) (Plugin, error) {
o := &errorPlugin{
err: err,
}
o.getter = defaultGetters(o, 1)
return o, nil
}
var _ StringGetter = (*errorPlugin)(nil)
func (o *errorPlugin) StringGetter() (func() (string, error), error) {
return func() (string, error) {
return "", o.err
}, nil
}
var _ IntSetter = (*errorPlugin)(nil)
func (o *errorPlugin) IntSetter(param string) (func(int64) error, error) {

View file

@ -245,8 +245,9 @@ func deviceInstanceFromMergedConfig[T any](ctx context.Context, id int, class te
}
type testResult = struct {
Value any `json:"value"`
Error string `json:"error"`
Value any `json:"value"`
Error string `json:"error"`
Asleep bool `json:"asleep,omitempty"`
}
func hasFeature(instance any, f api.Feature) bool {
@ -266,7 +267,12 @@ func testInstance(ctx context.Context, instance any) map[string]testResult {
if errors.Is(err, api.ErrNotAvailable) {
return
}
tr.Error = err.Error()
// asleep is a valid vehicle state, not an error
if errors.Is(err, api.ErrAsleep) {
tr.Asleep = true
} else {
tr.Error = err.Error()
}
}
resMu.Lock()
res[key] = tr

View file

@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/api/globalconfig"
"github.com/evcc-io/evcc/plugin/mqtt"
"github.com/evcc-io/evcc/util/config"
@ -66,6 +67,24 @@ func TestInstanceParallelProbes(t *testing.T) {
require.NotContains(t, res, "power", "blocking getter must be abandoned")
}
// asleepVehicle returns api.ErrAsleep from its getters.
type asleepVehicle struct{}
func (v asleepVehicle) Soc() (float64, error) {
return 0, api.ErrAsleep
}
func (v asleepVehicle) Range() (int64, error) {
return 0, api.ErrAsleep
}
// TestInstanceAsleep ensures asleep is flagged as state, not as error.
func TestInstanceAsleep(t *testing.T) {
res := testInstance(context.Background(), asleepVehicle{})
assert.Equal(t, testResult{Value: 0.0, Asleep: true}, res["soc"])
assert.Equal(t, testResult{Value: int64(0), Asleep: true}, res["range"])
}
func TestConfigReqUnmarshal(t *testing.T) {
var req configReq
require.NoError(t, json.Unmarshal([]byte(`{

View file

@ -0,0 +1,8 @@
vehicles:
- name: sleepy
type: custom
title: Sleepy Car
capacity: 68
soc:
source: error
error: ErrAsleep

View file

@ -0,0 +1,25 @@
import { test, expect } from "@playwright/test";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
test.beforeEach(async () => {
await start("vehicle-asleep.evcc.yaml");
});
test.afterEach(async () => {
await stop();
});
test.describe("asleep vehicle", async () => {
test("config: values show sleeping state instead of errors", async ({ page }) => {
await page.goto("/#/config");
const vehicle = page.getByTestId("vehicle");
await expect(vehicle).toHaveCount(1);
await expect(vehicle).toContainText("Sleepy Car");
await expect(vehicle.getByTestId("device-tag-capacity")).toContainText("68.0 kWh");
await expect(vehicle.getByTestId("device-tag-soc")).toContainText("sleeping");
await expect(page.getByTestId("header")).toBeVisible();
});
});