Custom vehicle: add charge controller (#13119)

This commit is contained in:
andig 2024-03-23 18:14:14 +01:00 • committed by GitHub
parent 6f8d0b8a89
commit a5c7b9fd64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1684 additions and 259 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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 {
@ -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 {

View file

@ -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])
}

View file

@ -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 {
@ -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)
},
}
@ -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)
}

View file

@ -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])
}

View file

@ -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
}

View file

@ -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])
}

View file

@ -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])
}

View file

@ -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)
}

View file

@ -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
@ -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
}
var err error
// StopCharge implements the api.VehicleChargeController interface
func (v *Controller) StopCharge() error {
if !sponsor.IsAuthorized() {
return api.ErrSponsorRequired
}
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())
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

View file

@ -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) {
@ -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
@ -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)
}

View file

@ -6,12 +6,12 @@ import (
"github.com/evcc-io/evcc/api"
)
func decorateTronity(base *Tronity, chargeState func() (api.ChargeStatus, error), vehicleOdometer func() (float64, error), vehicleStartCharge func() error, vehicleStopCharge func() error) api.Vehicle {
func decorateTronity(base *Tronity, chargeState func() (api.ChargeStatus, error), vehicleOdometer func() (float64, error), chargeController func(bool) error) api.Vehicle {
switch {
case chargeState == nil && vehicleOdometer == nil && vehicleStartCharge == nil && vehicleStopCharge == nil:
case chargeController == nil && chargeState == nil && vehicleOdometer == nil:
return base
case chargeState != nil && vehicleOdometer == nil && vehicleStartCharge == nil && vehicleStopCharge == nil:
case chargeController == nil && chargeState != nil && vehicleOdometer == nil:
return &struct {
*Tronity
api.ChargeState
@ -22,7 +22,7 @@ func decorateTronity(base *Tronity, chargeState func() (api.ChargeStatus, error)
},
}
case chargeState == nil && vehicleOdometer != nil && vehicleStartCharge == nil && vehicleStopCharge == nil:
case chargeController == nil && chargeState == nil && vehicleOdometer != nil:
return &struct {
*Tronity
api.VehicleOdometer
@ -33,7 +33,7 @@ func decorateTronity(base *Tronity, chargeState func() (api.ChargeStatus, error)
},
}
case chargeState != nil && vehicleOdometer != nil && vehicleStartCharge == nil && vehicleStopCharge == nil:
case chargeController == nil && chargeState != nil && vehicleOdometer != nil:
return &struct {
*Tronity
api.ChargeState
@ -48,85 +48,78 @@ func decorateTronity(base *Tronity, chargeState func() (api.ChargeStatus, error)
},
}
case chargeState == nil && vehicleOdometer == nil && vehicleStartCharge != nil && vehicleStopCharge != nil:
case chargeController != nil && chargeState == nil && vehicleOdometer == nil:
return &struct {
*Tronity
api.VehicleChargeController
api.ChargeController
}{
Tronity: base,
VehicleChargeController: &decorateTronityVehicleChargeControllerImpl{
vehicleStartCharge: vehicleStartCharge,
ChargeController: &decorateTronityChargeControllerImpl{
chargeController: chargeController,
},
}
case chargeState == nil && vehicleOdometer == nil && vehicleStartCharge != nil && vehicleStopCharge != nil:
return &struct {
*Tronity
api.VehicleChargeController
}{
Tronity: base,
VehicleChargeController: &decorateTronityVehicleChargeControllerImpl{
vehicleStartCharge: vehicleStartCharge,
vehicleStopCharge: vehicleStopCharge,
},
}
case chargeState != nil && vehicleOdometer == nil && vehicleStartCharge != nil && vehicleStopCharge != nil:
case chargeController != nil && chargeState != nil && vehicleOdometer == nil:
return &struct {
*Tronity
api.ChargeController
api.ChargeState
api.VehicleChargeController
}{
Tronity: base,
ChargeController: &decorateTronityChargeControllerImpl{
chargeController: chargeController,
},
ChargeState: &decorateTronityChargeStateImpl{
chargeState: chargeState,
},
VehicleChargeController: &decorateTronityVehicleChargeControllerImpl{
vehicleStartCharge: vehicleStartCharge,
vehicleStopCharge: vehicleStopCharge,
},
}
case chargeState == nil && vehicleOdometer != nil && vehicleStartCharge != nil && vehicleStopCharge != nil:
case chargeController != nil && chargeState == nil && vehicleOdometer != nil:
return &struct {
*Tronity
api.ChargeController
api.VehicleOdometer
api.VehicleChargeController
}{
Tronity: base,
ChargeController: &decorateTronityChargeControllerImpl{
chargeController: chargeController,
},
VehicleOdometer: &decorateTronityVehicleOdometerImpl{
vehicleOdometer: vehicleOdometer,
},
VehicleChargeController: &decorateTronityVehicleChargeControllerImpl{
vehicleStartCharge: vehicleStartCharge,
vehicleStopCharge: vehicleStopCharge,
},
}
case chargeState != nil && vehicleOdometer != nil && vehicleStartCharge != nil && vehicleStopCharge != nil:
case chargeController != nil && chargeState != nil && vehicleOdometer != nil:
return &struct {
*Tronity
api.ChargeController
api.ChargeState
api.VehicleOdometer
api.VehicleChargeController
}{
Tronity: base,
ChargeController: &decorateTronityChargeControllerImpl{
chargeController: chargeController,
},
ChargeState: &decorateTronityChargeStateImpl{
chargeState: chargeState,
},
VehicleOdometer: &decorateTronityVehicleOdometerImpl{
vehicleOdometer: vehicleOdometer,
},
VehicleChargeController: &decorateTronityVehicleChargeControllerImpl{
vehicleStartCharge: vehicleStartCharge,
vehicleStopCharge: vehicleStopCharge,
},
}
}
return nil
}
type decorateTronityChargeControllerImpl struct {
chargeController func(bool) error
}
func (impl *decorateTronityChargeControllerImpl) ChargeEnable(p0 bool) error {
return impl.chargeController(p0)
}
type decorateTronityChargeStateImpl struct {
chargeState func() (api.ChargeStatus, error)
}
@ -142,16 +135,3 @@ type decorateTronityVehicleOdometerImpl struct {
func (impl *decorateTronityVehicleOdometerImpl) Odometer() (float64, error) {
return impl.vehicleOdometer()
}
type decorateTronityVehicleChargeControllerImpl struct {
vehicleStartCharge func() error
vehicleStopCharge func() error
}
func (impl *decorateTronityVehicleChargeControllerImpl) StartCharge() error {
return impl.vehicleStartCharge()
}
func (impl *decorateTronityVehicleChargeControllerImpl) StopCharge() error {
return impl.vehicleStopCharge()
}

View file

@ -8,7 +8,7 @@ import (
"github.com/evcc-io/evcc/util"
)
//go:generate go run ../cmd/tools/decorate.go -f decorateVehicle -b api.Vehicle -t "api.ChargeState,Status,func() (api.ChargeStatus, error)" -t "api.VehicleRange,Range,func() (int64, error)" -t "api.VehicleOdometer,Odometer,func() (float64, error)" -t "api.VehicleClimater,Climater,func() (bool, error)" -t "api.CurrentController,MaxCurrent,func(int64) error" -t "api.Resurrector,WakeUp,func() error"
//go:generate go run ../cmd/tools/decorate.go -f decorateVehicle -b api.Vehicle -t "api.ChargeState,Status,func() (api.ChargeStatus, error)" -t "api.VehicleRange,Range,func() (int64, error)" -t "api.VehicleOdometer,Odometer,func() (float64, error)" -t "api.VehicleClimater,Climater,func() (bool, error)" -t "api.CurrentController,MaxCurrent,func(int64) error" -t "api.Resurrector,WakeUp,func() error" -t "api.ChargeController,ChargeEnable,func(bool) error"
// Vehicle is an api.Vehicle implementation with configurable getters and setters.
type Vehicle struct {
@ -23,14 +23,15 @@ func init() {
// NewConfigurableFromConfig creates a new Vehicle
func NewConfigurableFromConfig(other map[string]interface{}) (api.Vehicle, error) {
var cc struct {
embed `mapstructure:",squash"`
Soc provider.Config
Status *provider.Config
Range *provider.Config
Odometer *provider.Config
Climater *provider.Config
MaxCurrent *provider.Config
Wakeup *provider.Config
embed `mapstructure:",squash"`
Soc provider.Config
Status *provider.Config
Range *provider.Config
Odometer *provider.Config
Climater *provider.Config
MaxCurrent *provider.Config
Wakeup *provider.Config
ChargeEnable *provider.Config
}
if err := util.DecodeOther(other, &cc); err != nil {
@ -115,7 +116,16 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Vehicle, error
}
}
return decorateVehicle(v, status, rng, odo, climater, maxCurrent, wakeup), nil
// decorate chargeEnable
var chargeEnable func(bool) error
if cc.ChargeEnable != nil {
chargeEnable, err = provider.NewBoolSetterFromConfig("chargeEnable", *cc.ChargeEnable)
if err != nil {
return nil, fmt.Errorf("chargeEnable: %w", err)
}
}
return decorateVehicle(v, status, rng, odo, climater, maxCurrent, wakeup, chargeEnable), nil
}
// Soc implements the api.Vehicle interface

File diff suppressed because it is too large Load diff

View file

@ -156,21 +156,17 @@ func (v *Provider) TargetSoc() (float64, error) {
return float64(*res.Charging.ChargingSettings.Value.TargetSOCPct), nil
}
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])
}
var _ api.Resurrector = (*Provider)(nil)
// WakeUp implements the api.Resurrector interface
func (v *Provider) WakeUp() error {
return v.StartCharge()
return v.ChargeEnable(true)
}

View file

@ -164,16 +164,12 @@ 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.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])
}
var _ api.Diagnosis = (*Provider)(nil)