Skip to content

Commit

Permalink
Custom vehicle: add charge controller (evcc-io#13119)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Mar 23, 2024
1 parent 6f8d0b8 commit a5c7b9f
Show file tree
Hide file tree
Showing 18 changed files with 1,684 additions and 259 deletions.
7 changes: 3 additions & 4 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,9 @@ type SocLimiter interface {
TargetSoc() (float64, error)
}

// VehicleChargeController allows to start/stop the charging session on the vehicle side
type VehicleChargeController interface {
StartCharge() error
StopCharge() error
// ChargeController allows to start/stop the charging session on the vehicle side
type ChargeController interface {
ChargeEnable(bool) error
}

// Resurrector provides wakeup calls to the vehicle with an API call or a CP interrupt from the charger
Expand Down
9 changes: 2 additions & 7 deletions charger/twc3.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,12 @@ func (c *Twc3) Enable(enable bool) error {
return nil
}

v, ok := c.lp.GetVehicle().(api.VehicleChargeController)
v, ok := c.lp.GetVehicle().(api.ChargeController)
if !ok {
return errors.New("vehicle not capable of start/stop")
}

if enable {
err = v.StartCharge()
} else {
err = v.StopCharge()
}

err = v.ChargeEnable(enable)
if err == nil {
c.enabled = enable
}
Expand Down
15 changes: 10 additions & 5 deletions cmd/soc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,18 @@ func main() {

switch action {
case "wakeup":
vv, ok := v.(api.VehicleChargeController)
if !ok {
switch vv := v.(type) {
case api.Resurrector:
if err := vv.WakeUp(); err != nil {
log.Fatal(err)
}
case api.ChargeController:
if err := vv.ChargeEnable(true); err != nil {
log.Fatal(err)
}
default:
log.Fatal("not supported:", action)
}
if err := vv.StartCharge(); err != nil {
log.Fatal(err)
}

case "soc":
var soc float64
Expand Down
8 changes: 4 additions & 4 deletions cmd/vehicle.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ func runVehicle(cmd *cobra.Command, args []string) {
if cmd.Flags().Lookup(flagStart).Changed {
flagUsed = true

if vv, ok := v.(api.VehicleChargeController); ok {
if err := vv.StartCharge(); err != nil {
if vv, ok := v.(api.ChargeController); ok {
if err := vv.ChargeEnable(true); err != nil {
log.ERROR.Println("start charge:", err)
}
} else {
Expand All @@ -100,8 +100,8 @@ func runVehicle(cmd *cobra.Command, args []string) {
if cmd.Flags().Lookup(flagStop).Changed {
flagUsed = true

if vv, ok := v.(api.VehicleChargeController); ok {
if err := vv.StopCharge(); err != nil {
if vv, ok := v.(api.ChargeController); ok {
if err := vv.ChargeEnable(false); err != nil {
log.ERROR.Println("stop charge:", err)
}
} else {
Expand Down
14 changes: 5 additions & 9 deletions vehicle/bmw/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,10 @@ func (v *Provider) WakeUp() error {
return v.actionS(DOOR_LOCK)
}

var _ api.VehicleChargeController = (*Provider)(nil)
var _ api.ChargeController = (*Provider)(nil)

// StartCharge implements the api.VehicleChargeController interface
func (v *Provider) StartCharge() error {
return v.actionS(CHARGE_START)
}

// StopCharge implements the api.VehicleChargeController interface
func (v *Provider) StopCharge() error {
return v.actionS(CHARGE_STOP)
// ChargeEnable implements the api.ChargeController interface
func (v *Provider) ChargeEnable(enable bool) error {
action := map[bool]string{true: CHARGE_START, false: CHARGE_STOP}
return v.actionS(action[enable])
}
19 changes: 7 additions & 12 deletions vehicle/jlr/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type Provider struct {
statusG func() (StatusResponse, error)
positionG func() (PositionResponse, error)
actionS func(bool) error
chargeS func(bool) error
}

func NewProvider(api *API, vin, user string, cache time.Duration) *Provider {
Expand All @@ -21,8 +21,8 @@ func NewProvider(api *API, vin, user string, cache time.Duration) *Provider {
positionG: provider.Cached(func() (PositionResponse, error) {
return api.Position(vin)
}, cache),
actionS: func(start bool) error {
return api.ChargeAction(vin, user, start)
chargeS: func(enable bool) error {
return api.ChargeAction(vin, user, enable)
},
}

Expand Down Expand Up @@ -116,14 +116,9 @@ func (v *Provider) Position() (float64, float64, error) {
return 0, 0, err
}

var _ api.VehicleChargeController = (*Provider)(nil)
var _ api.ChargeController = (*Provider)(nil)

// StartCharge implements the api.VehicleChargeController interface
func (v *Provider) StartCharge() error {
return v.actionS(true)
}

// StopCharge implements the api.VehicleChargeController interface
func (v *Provider) StopCharge() error {
return v.actionS(false)
// ChargeEnable implements the api.ChargeController interface
func (v *Provider) ChargeEnable(enable bool) error {
return v.chargeS(enable)
}
14 changes: 5 additions & 9 deletions vehicle/nissan/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,10 @@ func (v *Provider) FinishTime() (time.Time, error) {
return time.Time{}, err
}

var _ api.VehicleChargeController = (*Provider)(nil)
var _ api.ChargeController = (*Provider)(nil)

// StartCharge implements the api.VehicleChargeController interface
func (v *Provider) StartCharge() error {
return v.action(ActionChargeStart)
}

// StopCharge implements the api.VehicleChargeController interface
func (v *Provider) StopCharge() error {
return v.action(ActionChargeStop)
// ChargeEnable implements the api.ChargeController interface
func (v *Provider) ChargeEnable(enable bool) error {
action := map[bool]Action{true: ActionChargeStart, false: ActionChargeStop}
return v.action(action[enable])
}
15 changes: 5 additions & 10 deletions vehicle/renault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,11 @@ func (v *Provider) Position() (float64, float64, error) {
return 0, 0, err
}

var _ api.VehicleChargeController = (*Provider)(nil)
var _ api.ChargeController = (*Provider)(nil)

// StartCharge implements the api.VehicleChargeController interface
func (v *Provider) StartCharge() error {
_, err := v.action(kamereon.ActionStart)
return err
}

// StopCharge implements the api.VehicleChargeController interface
func (v *Provider) StopCharge() error {
_, err := v.action(kamereon.ActionStop)
// ChargeEnable implements the api.ChargeController interface
func (v *Provider) ChargeEnable(enable bool) error {
action := map[bool]string{true: kamereon.ActionStart, false: kamereon.ActionStop}
_, err := v.action(action[enable])
return err
}
14 changes: 5 additions & 9 deletions vehicle/seat/cupra/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,10 @@ func (v *Provider) TargetSoc() (float64, error) {
return float64(res.Services.Charging.TargetPct), err
}

var _ api.VehicleChargeController = (*Provider)(nil)
var _ api.ChargeController = (*Provider)(nil)

// StartCharge implements the api.VehicleChargeController interface
func (v *Provider) StartCharge() error {
return v.action(ActionCharge, ActionChargeStart)
}

// StopCharge implements the api.VehicleChargeController interface
func (v *Provider) StopCharge() error {
return v.action(ActionCharge, ActionChargeStop)
// ChargeEnable implements the api.ChargeController interface
func (v *Provider) ChargeEnable(enable bool) error {
action := map[bool]string{true: ActionChargeStart, false: ActionChargeStop}
return v.action(ActionCharge, action[enable])
}
14 changes: 5 additions & 9 deletions vehicle/skoda/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,10 @@ func (v *Provider) TargetSoc() (float64, error) {
return 0, err
}

var _ api.VehicleChargeController = (*Provider)(nil)
var _ api.ChargeController = (*Provider)(nil)

// StartCharge implements the api.VehicleChargeController interface
func (v *Provider) StartCharge() error {
return v.action(ActionCharge, ActionChargeStart)
}

// StopCharge implements the api.VehicleChargeController interface
func (v *Provider) StopCharge() error {
return v.action(ActionCharge, ActionChargeStop)
// ChargeEnable implements the api.ChargeController interface
func (v *Provider) ChargeEnable(enable bool) error {
action := map[bool]string{true: ActionChargeStart, false: ActionChargeStop}
return v.action(ActionCharge, action[enable])
}
2 changes: 1 addition & 1 deletion vehicle/tesla/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ func TestCommandResponse(t *testing.T) {
v, err := client.Vehicle("abc")
require.NoError(t, err)

require.ErrorIs(t, NewController(v).StartCharge(), api.ErrAsleep)
require.ErrorIs(t, NewController(v).ChargeEnable(true), api.ErrAsleep)
}
36 changes: 16 additions & 20 deletions vehicle/tesla/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewController(vehicle *tesla.Vehicle) *Controller {

var _ api.CurrentController = (*Controller)(nil)

// StartCharge implements the api.VehicleChargeController interface
// MaxCurrent implements the api.CurrentController interface
func (v *Controller) MaxCurrent(current int64) error {
if !sponsor.IsAuthorized() {
return api.ErrSponsorRequired
Expand All @@ -34,32 +34,28 @@ func (v *Controller) MaxCurrent(current int64) error {
return apiError(v.vehicle.SetChargingAmps(int(current)))
}

var _ api.VehicleChargeController = (*Controller)(nil)
var _ api.ChargeController = (*Controller)(nil)

// StartCharge implements the api.VehicleChargeController interface
func (v *Controller) StartCharge() error {
// ChargeEnable implements the api.ChargeController interface
func (v *Controller) ChargeEnable(enable bool) error {
if !sponsor.IsAuthorized() {
return api.ErrSponsorRequired
}

err := apiError(v.vehicle.StartCharging())
if err != nil && slices.Contains([]string{"complete", "is_charging"}, err.Error()) {
return nil
}
return err
}

// StopCharge implements the api.VehicleChargeController interface
func (v *Controller) StopCharge() error {
if !sponsor.IsAuthorized() {
return api.ErrSponsorRequired
}
var err error

err := apiError(v.vehicle.StopCharging())
if enable {
err = apiError(v.vehicle.StartCharging())
if err != nil && slices.Contains([]string{"complete", "is_charging"}, err.Error()) {
return nil
}
} else {
err = apiError(v.vehicle.StopCharging())

// ignore sleeping vehicle
if errors.Is(err, api.ErrAsleep) {
err = nil
// ignore sleeping vehicle
if errors.Is(err, api.ErrAsleep) {
err = nil
}
}

return err
Expand Down
22 changes: 8 additions & 14 deletions vehicle/tronity.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func init() {
registry.Add("tronity", NewTronityFromConfig)
}

// go:generate go run ../cmd/tools/decorate.go -f decorateTronity -b *Tronity -r api.Vehicle -t "api.ChargeState,Status,func() (api.ChargeStatus, error)" -t "api.VehicleOdometer,Odometer,func() (float64, error)" -t "api.VehicleChargeController,StartCharge,func() error" -t "api.VehicleChargeController,StopCharge,func() error"
//go:generate go run ../cmd/tools/decorate.go -f decorateTronity -b *Tronity -r api.Vehicle -t "api.ChargeState,Status,func() (api.ChargeStatus, error)" -t "api.VehicleOdometer,Odometer,func() (float64, error)" -t "api.ChargeController,ChargeEnable,func(bool) error"

// NewTronityFromConfig creates a new vehicle
func NewTronityFromConfig(other map[string]interface{}) (api.Vehicle, error) {
Expand Down Expand Up @@ -131,13 +131,12 @@ func NewTronityFromConfig(other map[string]interface{}) (api.Vehicle, error) {
odometer = v.odometer
}

var start, stop func() error
var chargeEnable func(bool) error
if slices.Contains(vehicle.Scopes, tronity.WriteChargeStartStop) {
start = v.startCharge
stop = v.stopCharge
chargeEnable = v.chargeEnable
}

return decorateTronity(v, status, odometer, start, stop), nil
return decorateTronity(v, status, odometer, chargeEnable), nil
}

// RefreshToken performs token refresh by logging in with app context
Expand Down Expand Up @@ -234,14 +233,9 @@ func (v *Tronity) post(uri string) error {
return err
}

// startCharge implements the api.VehicleChargeController interface
func (v *Tronity) startCharge() error {
uri := fmt.Sprintf("%s/tronity/vehicles/%s/start_charging", tronity.URI, v.vid)
return v.post(uri)
}

// stopCharge implements the api.VehicleChargeController interface
func (v *Tronity) stopCharge() error {
uri := fmt.Sprintf("%s/tronity/vehicles/%s/stop_charging", tronity.URI, v.vid)
// chargeEnable implements the api.ChargeController interface
func (v *Tronity) chargeEnable(enable bool) error {
action := map[bool]string{true: "start_charging", false: "stop_charging"}
uri := fmt.Sprintf("%s/tronity/vehicles/%s/%s", tronity.URI, v.vid, action[enable])
return v.post(uri)
}
Loading

0 comments on commit a5c7b9f

Please sign in to comment.