Teslamate: add gps position (#28626)
This commit is contained in:
parent
c4f6e56fc8
commit
01e095621a
6 changed files with 91 additions and 4 deletions
|
|
@ -70,6 +70,7 @@ func init() {
|
|||
reflect.TypeFor[api.VehicleRange](),
|
||||
reflect.TypeFor[api.VehicleClimater](),
|
||||
reflect.TypeFor[api.VehicleFinishTimer](),
|
||||
reflect.TypeFor[api.VehiclePosition](),
|
||||
reflect.TypeFor[api.Identifier](),
|
||||
reflect.TypeFor[api.ChargerEx](),
|
||||
reflect.TypeFor[api.ChargeRater](),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,15 @@ render: |
|
|||
source: mqtt
|
||||
topic: teslamate/cars/{{ .id }}/odometer
|
||||
timeout: {{ .timeout }}
|
||||
position:
|
||||
latitude:
|
||||
source: mqtt
|
||||
topic: teslamate/cars/{{ .id }}/latitude
|
||||
timeout: {{ .timeout }}
|
||||
longitude:
|
||||
source: mqtt
|
||||
topic: teslamate/cars/{{ .id }}/longitude
|
||||
timeout: {{ .timeout }}
|
||||
limitsoc:
|
||||
source: mqtt
|
||||
topic: teslamate/cars/{{ .id }}/charge_limit_soc
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
|
|||
wakeup,
|
||||
enable,
|
||||
nil,
|
||||
nil,
|
||||
), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,api.ChargeRater
|
||||
//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,api.VehiclePosition
|
||||
|
||||
// Vehicle is an api.Vehicle implementation with configurable getters and setters.
|
||||
type Vehicle struct {
|
||||
|
|
@ -39,6 +39,10 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V
|
|||
Wakeup *plugin.Config
|
||||
ChargeEnable *plugin.Config
|
||||
ChargedEnergy *plugin.Config
|
||||
Position *struct {
|
||||
Latitude plugin.Config `mapstructure:"latitude"`
|
||||
Longitude plugin.Config `mapstructure:"longitude"`
|
||||
} `mapstructure:"position"`
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -107,6 +111,34 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.V
|
|||
return nil, fmt.Errorf("getMaxCurrent: %w", err)
|
||||
}
|
||||
|
||||
// decorate position
|
||||
var position func() (float64, float64, error)
|
||||
if cc.Position != nil {
|
||||
latG, err := cc.Position.Latitude.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("latitude: %w", err)
|
||||
}
|
||||
lonG, err := cc.Position.Longitude.FloatGetter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("longitude: %w", err)
|
||||
}
|
||||
position = func() (float64, float64, error) {
|
||||
lat, err := latG()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
lon, err := lonG()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// MQTT sources may report (0,0) when no GPS fix is available
|
||||
if lat == 0 && lon == 0 {
|
||||
return 0, 0, api.ErrNotAvailable
|
||||
}
|
||||
return lat, lon, nil
|
||||
}
|
||||
}
|
||||
|
||||
// decorate finishtime
|
||||
var finishTime func() (time.Time, error)
|
||||
if cc.FinishTime != nil {
|
||||
|
|
@ -160,7 +192,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, chargedEnergy), nil
|
||||
return decorateVehicle(v, limitSoc, status, rng, odo, climater, maxCurrent, getMaxCurrent, finishTime, wakeup, chargeEnable, chargedEnergy, position), nil
|
||||
}
|
||||
|
||||
// Soc implements the api.Vehicle interface
|
||||
|
|
|
|||
|
|
@ -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, chargeRater func() (float64, 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), vehiclePosition func() (float64, float64, error)) api.Vehicle {
|
||||
caps := make(map[reflect.Type]any)
|
||||
|
||||
if socLimiter != nil {
|
||||
|
|
@ -56,6 +56,10 @@ func decorateVehicle(base api.Vehicle, socLimiter func() (int64, error), chargeS
|
|||
caps[reflect.TypeFor[api.ChargeRater]()] = &decorateVehicleChargeRaterImpl{chargeRater: chargeRater}
|
||||
}
|
||||
|
||||
if vehiclePosition != nil {
|
||||
caps[reflect.TypeFor[api.VehiclePosition]()] = &decorateVehicleVehiclePositionImpl{vehiclePosition: vehiclePosition}
|
||||
}
|
||||
|
||||
if len(caps) == 0 {
|
||||
return base
|
||||
}
|
||||
|
|
@ -153,6 +157,14 @@ func (impl *decorateVehicleVehicleOdometerImpl) Odometer() (float64, error) {
|
|||
return impl.vehicleOdometer()
|
||||
}
|
||||
|
||||
type decorateVehicleVehiclePositionImpl struct {
|
||||
vehiclePosition func() (float64, float64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateVehicleVehiclePositionImpl) Position() (float64, float64, error) {
|
||||
return impl.vehiclePosition()
|
||||
}
|
||||
|
||||
type decorateVehicleVehicleRangeImpl struct {
|
||||
vehicleRange func() (int64, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, nil)
|
||||
v := decorateVehicle(baseVehicle(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
|
||||
// base vehicle interface works
|
||||
soc, err := v.Soc()
|
||||
|
|
@ -43,6 +43,7 @@ func TestDecorateVehicle_AllCapabilities(t *testing.T) {
|
|||
func() error { return nil }, // resurrector
|
||||
func(bool) error { return nil }, // chargeController
|
||||
nil, // chargeRater
|
||||
func() (float64, float64, error) { return 50.1234, 8.5678, nil }, // vehiclePosition
|
||||
)
|
||||
|
||||
// base interface
|
||||
|
|
@ -109,12 +110,42 @@ func TestDecorateVehicle_AllCapabilities(t *testing.T) {
|
|||
require.True(t, ok, "Resurrector")
|
||||
assert.NoError(t, r.WakeUp())
|
||||
|
||||
// VehiclePosition
|
||||
vp, ok := api.Cap[api.VehiclePosition](v)
|
||||
require.True(t, ok, "VehiclePosition")
|
||||
lat, lon, err := vp.Position()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 50.1234, lat)
|
||||
assert.Equal(t, 8.5678, lon)
|
||||
|
||||
// ChargeController
|
||||
chc, ok := api.Cap[api.ChargeController](v)
|
||||
require.True(t, ok, "ChargeController")
|
||||
assert.NoError(t, chc.ChargeEnable(true))
|
||||
}
|
||||
|
||||
func TestDecorateVehicle_PositionNotAvailable(t *testing.T) {
|
||||
v := decorateVehicle(baseVehicle(),
|
||||
nil, // socLimiter
|
||||
nil, // chargeState
|
||||
nil, // vehicleRange
|
||||
nil, // vehicleOdometer
|
||||
nil, // vehicleClimater
|
||||
nil, // currentController
|
||||
nil, // currentGetter
|
||||
nil, // vehicleFinishTimer
|
||||
nil, // resurrector
|
||||
nil, // chargeController
|
||||
nil, // chargeRater
|
||||
func() (float64, float64, error) { return 0, 0, api.ErrNotAvailable }, // vehiclePosition
|
||||
)
|
||||
|
||||
vp, ok := api.Cap[api.VehiclePosition](v)
|
||||
require.True(t, ok, "VehiclePosition")
|
||||
_, _, err := vp.Position()
|
||||
assert.ErrorIs(t, err, api.ErrNotAvailable)
|
||||
}
|
||||
|
||||
func TestDecorateVehicle_PartialCapabilities(t *testing.T) {
|
||||
v := decorateVehicle(baseVehicle(),
|
||||
func() (int64, error) { return 80, nil }, // socLimiter
|
||||
|
|
@ -128,6 +159,7 @@ func TestDecorateVehicle_PartialCapabilities(t *testing.T) {
|
|||
func() error { return nil }, // resurrector
|
||||
nil, // chargeController
|
||||
nil, // chargeRater
|
||||
nil, // vehiclePosition
|
||||
)
|
||||
|
||||
assert.True(t, api.HasCap[api.SocLimiter](v), "SocLimiter should be present")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue