Fiat: support start/stop charge on TWC3 (#18028)
This commit is contained in:
parent
2cebf20711
commit
d8419c651a
5 changed files with 159 additions and 3 deletions
|
|
@ -9,13 +9,13 @@ params:
|
|||
- name: pin
|
||||
mask: true
|
||||
help:
|
||||
en: Required for evcc to refresh the SoC while charging.
|
||||
de: Benötigt zur Aktualisierung des Ladestands während der Ladung.
|
||||
en: Required for evcc to refresh the SoC while charging. When connected to TWC3, used to start/stop charging.
|
||||
de: Benötigt zur Aktualisierung des Ladestands während der Ladung. Bei Nutzung mit TWC3 kann der Ladevorgang mittels PIN gestartet und gestoppt werden.
|
||||
- preset: vehicle-features
|
||||
render: |
|
||||
type: fiat
|
||||
{{ include "vehicle-base" . }}
|
||||
{{- if .pin }}
|
||||
pin: {{ .pin }} # mandatory to deep refresh Soc
|
||||
pin: {{ .pin }} # mandatory to deep refresh Soc & start/stop charge
|
||||
{{- end }}
|
||||
{{ include "vehicle-features" . }}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
type Fiat struct {
|
||||
*embed
|
||||
*fiat.Provider
|
||||
*fiat.Controller
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -59,6 +60,7 @@ func NewFiatFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
|
||||
if err == nil {
|
||||
v.Provider = fiat.NewProvider(api, cc.VIN, cc.PIN, cc.Expiry, cc.Cache)
|
||||
v.Controller = fiat.NewController(v.Provider, api, cc.VIN, cc.PIN)
|
||||
}
|
||||
|
||||
return v, err
|
||||
|
|
|
|||
|
|
@ -158,3 +158,35 @@ func (v *API) Action(vin, pin, action, cmd string) (ActionResponse, error) {
|
|||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (v *API) UpdateSchedule(vin, pin string, schedules []Schedule) (ActionResponse, error) {
|
||||
var res ActionResponse
|
||||
|
||||
token, err := v.pinAuth(pin)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/v2/accounts/%s/vehicles/%s/ev/schedule", ApiURI, v.identity.UID(), vin)
|
||||
|
||||
data := struct {
|
||||
Command string `json:"command"`
|
||||
PinAuth string `json:"pinAuth"`
|
||||
Schedules []Schedule `json:"schedules"`
|
||||
}{
|
||||
Command: "CPPLUS",
|
||||
PinAuth: token,
|
||||
Schedules: schedules,
|
||||
}
|
||||
|
||||
req, err := v.request(http.MethodPost, uri, request.MarshalJSON(data))
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
if err == nil && res.Message != "" {
|
||||
err = fmt.Errorf("action schedules: %s", res.Message)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
|
|
|||
102
vehicle/fiat/controller.go
Normal file
102
vehicle/fiat/controller.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package fiat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
pvd *Provider
|
||||
api *API
|
||||
vin string
|
||||
pin string
|
||||
}
|
||||
|
||||
// NewController creates a vehicle current and charge controller
|
||||
func NewController(provider *Provider, api *API, vin string, pin string) *Controller {
|
||||
impl := &Controller{
|
||||
pvd: provider,
|
||||
api: api,
|
||||
vin: vin,
|
||||
pin: pin,
|
||||
}
|
||||
return impl
|
||||
}
|
||||
|
||||
var _ api.CurrentController = (*Controller)(nil)
|
||||
|
||||
// MaxCurrent implements the api.CurrentController interface
|
||||
func (c *Controller) MaxCurrent(current int64) error {
|
||||
// Even if we cannot control the current, this interface must be implemented otherwise the ChargeEnable is never called
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ api.ChargeController = (*Controller)(nil)
|
||||
|
||||
// ChargeEnable implements the api.ChargeController interface
|
||||
func (c *Controller) ChargeEnable(enable bool) error {
|
||||
if c.pin == "" {
|
||||
return api.ErrMissingCredentials
|
||||
}
|
||||
|
||||
// get current schedule status from provider (cached)
|
||||
stat, err := c.pvd.statusG()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.EvInfo == nil || stat.EvInfo.Schedules == nil || len(stat.EvInfo.Schedules) == 0 {
|
||||
return api.ErrNotAvailable
|
||||
}
|
||||
|
||||
// configure first schedule and make sure it's active
|
||||
c.configureChargeSchedule(&stat.EvInfo.Schedules[0])
|
||||
|
||||
if enable {
|
||||
// start charging by updating active charge schedule to start now and end in 12h
|
||||
stat.EvInfo.Schedules[0].StartTime = time.Now().Format("15:04") // only hour and minutes
|
||||
stat.EvInfo.Schedules[0].EndTime = time.Now().Add(time.Hour * 12).Format("15:04") // only hour and minutes
|
||||
} else {
|
||||
// stop charging by updating active charge schedule end time to now
|
||||
stat.EvInfo.Schedules[0].EndTime = time.Now().Format("15:04") // only hour and minutes
|
||||
}
|
||||
|
||||
// make sure the other charge schedules are disabled in case user changed them
|
||||
c.disableConflictingChargeSchedule(&stat.EvInfo.Schedules[1])
|
||||
c.disableConflictingChargeSchedule(&stat.EvInfo.Schedules[2])
|
||||
|
||||
// post new schedule
|
||||
res, err := c.api.UpdateSchedule(c.vin, c.pin, stat.EvInfo.Schedules)
|
||||
if err == nil && res.ResponseStatus != "pending" {
|
||||
err = fmt.Errorf("invalid response status: %s", res.ResponseStatus)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Controller) configureChargeSchedule(schedule *Schedule) {
|
||||
// all values are set to be sure no manual change can lead to inconsistencies
|
||||
schedule.CabinPriority = false
|
||||
schedule.ChargeToFull = false
|
||||
schedule.EnableScheduleType = true
|
||||
schedule.RepeatSchedule = true
|
||||
schedule.ScheduleType = "CHARGE"
|
||||
|
||||
// only enable for current day to avoid undesired charge start in the future
|
||||
weekday := time.Now().Weekday()
|
||||
schedule.ScheduledDays.Monday = (weekday == time.Monday)
|
||||
schedule.ScheduledDays.Tuesday = (weekday == time.Tuesday)
|
||||
schedule.ScheduledDays.Wednesday = (weekday == time.Wednesday)
|
||||
schedule.ScheduledDays.Thursday = (weekday == time.Thursday)
|
||||
schedule.ScheduledDays.Friday = (weekday == time.Friday)
|
||||
schedule.ScheduledDays.Saturday = (weekday == time.Saturday)
|
||||
schedule.ScheduledDays.Sunday = (weekday == time.Sunday)
|
||||
}
|
||||
|
||||
func (c *Controller) disableConflictingChargeSchedule(schedule *Schedule) {
|
||||
// make sure the other charge schedules are disabled in case user changed them
|
||||
if schedule.ScheduleType == "CHARGE" && schedule.EnableScheduleType {
|
||||
schedule.EnableScheduleType = false
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,25 @@ type Vehicle struct {
|
|||
VIN string
|
||||
}
|
||||
|
||||
type Schedule struct {
|
||||
CabinPriority bool `json:"cabinPriority"`
|
||||
ChargeToFull bool `json:"chargeToFull"`
|
||||
EnableScheduleType bool `json:"enableScheduleType"`
|
||||
EndTime string `json:"endTime"`
|
||||
RepeatSchedule bool `json:"repeatSchedule"`
|
||||
ScheduleType string `json:"scheduleType"`
|
||||
ScheduledDays struct {
|
||||
Friday bool `json:"friday"`
|
||||
Monday bool `json:"monday"`
|
||||
Saturday bool `json:"saturday"`
|
||||
Sunday bool `json:"sunday"`
|
||||
Thursday bool `json:"thursday"`
|
||||
Tuesday bool `json:"tuesday"`
|
||||
Wednesday bool `json:"wednesday"`
|
||||
} `json:"scheduledDays"`
|
||||
StartTime string `json:"startTime"`
|
||||
}
|
||||
|
||||
type StatusResponse struct {
|
||||
VehicleInfo struct {
|
||||
Odometer struct {
|
||||
|
|
@ -52,6 +71,7 @@ type StatusResponse struct {
|
|||
TimeToFullyChargeL2 int // 540
|
||||
TotalRange int // 17
|
||||
}
|
||||
Schedules []Schedule
|
||||
Timestamp TimeMillis
|
||||
} `json:",omitempty"`
|
||||
Timestamp TimeMillis
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue