diff --git a/templates/definition/vehicle/teslamate.yaml b/templates/definition/vehicle/teslamate.yaml index 1ff42e4ac..022cb00e0 100644 --- a/templates/definition/vehicle/teslamate.yaml +++ b/templates/definition/vehicle/teslamate.yaml @@ -51,6 +51,10 @@ render: | topic: teslamate/cars/{{ .id }}/time_to_full_charge timeout: {{ .timeout }} jq: if (. == null) or (. == 0) or (type != "number") or (. < 0) then null else (now + (. * 3600) | strftime("%Y-%m-%dT%H:%M:%SZ")) end + chargedenergy: + source: mqtt + topic: teslamate/cars/{{ .id }}/charge_energy_added + timeout: {{ .timeout }} climater: source: mqtt topic: teslamate/cars/{{ .id }}/is_preconditioning diff --git a/vehicle/homeassistant.go b/vehicle/homeassistant.go index e54a86614..b58051bf4 100644 --- a/vehicle/homeassistant.go +++ b/vehicle/homeassistant.go @@ -128,6 +128,7 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error finish, wakeup, enable, + nil, ), nil } diff --git a/vehicle/vehicle.go b/vehicle/vehicle.go index 82877862e..62b2bfa8b 100644 --- a/vehicle/vehicle.go +++ b/vehicle/vehicle.go @@ -11,7 +11,7 @@ import ( "github.com/evcc-io/evcc/util" ) -//go:generate go tool decorate -f decorateVehicle -b api.Vehicle -t api.SocLimiter,api.ChargeState,api.VehicleRange,api.VehicleOdometer,api.VehicleClimater,api.CurrentController,api.CurrentGetter,api.VehicleFinishTimer,api.Resurrector,api.ChargeController +//go:generate go tool decorate -f decorateVehicle -b api.Vehicle -t api.SocLimiter,api.ChargeState,api.VehicleRange,api.VehicleOdometer,api.VehicleClimater,api.CurrentController,api.CurrentGetter,api.VehicleFinishTimer,api.Resurrector,api.ChargeController,api.ChargeRater // Vehicle is an api.Vehicle implementation with configurable getters and setters. type Vehicle struct { @@ -38,6 +38,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V FinishTime *plugin.Config Wakeup *plugin.Config ChargeEnable *plugin.Config + ChargedEnergy *plugin.Config } if err := util.DecodeOther(other, &cc); err != nil { @@ -140,6 +141,16 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V return nil, fmt.Errorf("chargeEnable: %w", err) } + // decorate chargedenergy + var chargedEnergy func() (float64, error) + if cc.ChargedEnergy != nil { + var err error + chargedEnergy, err = cc.ChargedEnergy.FloatGetter(ctx) + if err != nil { + return nil, fmt.Errorf("charged energy: %w", err) + } + } + switch { case maxCurrent == nil && getMaxCurrent != nil: return nil, errors.New("cannot have current without current control") @@ -149,7 +160,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V return nil, errors.New("cannot have charge control without status") } - return decorateVehicle(v, limitSoc, status, rng, odo, climater, maxCurrent, getMaxCurrent, finishTime, wakeup, chargeEnable), nil + return decorateVehicle(v, limitSoc, status, rng, odo, climater, maxCurrent, getMaxCurrent, finishTime, wakeup, chargeEnable, chargedEnergy), nil } // Soc implements the api.Vehicle interface diff --git a/vehicle/vehicle_decorators.go b/vehicle/vehicle_decorators.go index 20f32673b..46a7c0426 100644 --- a/vehicle/vehicle_decorators.go +++ b/vehicle/vehicle_decorators.go @@ -9,7 +9,7 @@ import ( "github.com/evcc-io/evcc/api" ) -func decorateVehicle(base api.Vehicle, socLimiter func() (int64, error), chargeState func() (api.ChargeStatus, error), vehicleRange func() (int64, error), vehicleOdometer func() (float64, error), vehicleClimater func() (bool, error), currentController func(int64) error, currentGetter func() (float64, error), vehicleFinishTimer func() (time.Time, error), resurrector func() error, chargeController func(bool) error) api.Vehicle { +func decorateVehicle(base api.Vehicle, socLimiter func() (int64, error), chargeState func() (api.ChargeStatus, error), vehicleRange func() (int64, error), vehicleOdometer func() (float64, error), vehicleClimater func() (bool, error), currentController func(int64) error, currentGetter func() (float64, error), vehicleFinishTimer func() (time.Time, error), resurrector func() error, chargeController func(bool) error, chargeRater func() (float64, error)) api.Vehicle { caps := make(map[reflect.Type]any) if socLimiter != nil { @@ -52,6 +52,10 @@ func decorateVehicle(base api.Vehicle, socLimiter func() (int64, error), chargeS caps[reflect.TypeFor[api.ChargeController]()] = &decorateVehicleChargeControllerImpl{chargeController: chargeController} } + if chargeRater != nil { + caps[reflect.TypeFor[api.ChargeRater]()] = &decorateVehicleChargeRaterImpl{chargeRater: chargeRater} + } + if len(caps) == 0 { return base } @@ -77,6 +81,14 @@ func (impl *decorateVehicleChargeControllerImpl) ChargeEnable(p0 bool) error { return impl.chargeController(p0) } +type decorateVehicleChargeRaterImpl struct { + chargeRater func() (float64, error) +} + +func (impl *decorateVehicleChargeRaterImpl) ChargedEnergy() (float64, error) { + return impl.chargeRater() +} + type decorateVehicleChargeStateImpl struct { chargeState func() (api.ChargeStatus, error) } diff --git a/vehicle/vehicle_decorators_test.go b/vehicle/vehicle_decorators_test.go index fd3044386..b262675a6 100644 --- a/vehicle/vehicle_decorators_test.go +++ b/vehicle/vehicle_decorators_test.go @@ -17,7 +17,7 @@ func baseVehicle() *Vehicle { } func TestDecorateVehicle_NoCapabilities(t *testing.T) { - v := decorateVehicle(baseVehicle(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + v := decorateVehicle(baseVehicle(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) // base vehicle interface works soc, err := v.Soc() @@ -42,6 +42,7 @@ func TestDecorateVehicle_AllCapabilities(t *testing.T) { func() (time.Time, error) { return time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), nil }, // vehicleFinishTimer func() error { return nil }, // resurrector func(bool) error { return nil }, // chargeController + nil, // chargeRater ) // base interface @@ -126,6 +127,7 @@ func TestDecorateVehicle_PartialCapabilities(t *testing.T) { nil, // vehicleFinishTimer func() error { return nil }, // resurrector nil, // chargeController + nil, // chargeRater ) assert.True(t, api.HasCap[api.SocLimiter](v), "SocLimiter should be present")