Fiat: improve charge scheduling and refresh logic (#27654)
This commit is contained in:
parent
87c5be3f55
commit
1a8e93d7c4
3 changed files with 398 additions and 31 deletions
|
|
@ -159,6 +159,7 @@ func (v *API) Action(vin, pin, action, cmd string) (ActionResponse, error) {
|
|||
return res, err
|
||||
}
|
||||
|
||||
// Warning: calling ChargeNow will start charging immediately and schedules will not be able to stop the charging.
|
||||
func (v *API) ChargeNow(vin, pin string) (ActionResponse, error) {
|
||||
return v.Action(vin, pin, "ev/chargenow", "CNOW")
|
||||
}
|
||||
|
|
@ -189,7 +190,7 @@ func (v *API) UpdateSchedule(vin, pin string, schedules []Schedule) (ActionRespo
|
|||
}
|
||||
|
||||
if err == nil && res.Message != "" {
|
||||
err = fmt.Errorf("action schedules: %s", res.Message)
|
||||
err = fmt.Errorf("unable to set action schedules: %s", res.Message)
|
||||
}
|
||||
|
||||
return res, err
|
||||
|
|
|
|||
|
|
@ -53,41 +53,40 @@ func (c *Controller) ChargeEnable(enable bool) error {
|
|||
return api.ErrNotAvailable
|
||||
}
|
||||
|
||||
// configure first schedule and make sure it's active
|
||||
c.configureChargeSchedule(&stat.EvInfo.Schedules[0])
|
||||
hasChanged := false // Will track if we made any change to the schedule to avoid unnecessary updates through API call
|
||||
now := time.Now()
|
||||
|
||||
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
|
||||
// Start charging from now until end of day (23:55)
|
||||
hasChanged = hasChanged || c.configureChargeSchedule(&stat.EvInfo.Schedules[0], now, time.Time{})
|
||||
} 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
|
||||
// Stop charging: set charge end time to now to stop charging as soon as possible; use empty time to keep start time as it was for history in Fiat app
|
||||
hasChanged = hasChanged || c.configureChargeSchedule(&stat.EvInfo.Schedules[0], time.Time{}, now)
|
||||
}
|
||||
|
||||
// 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])
|
||||
hasChanged = hasChanged || c.disableConflictingChargeSchedule(&stat.EvInfo.Schedules[1])
|
||||
hasChanged = hasChanged || 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)
|
||||
// post new schedule, but only if something changed to avoid unnecessary API calls
|
||||
if hasChanged {
|
||||
res, err := c.api.UpdateSchedule(c.vin, c.pin, stat.EvInfo.Schedules)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update schedule: %w", err)
|
||||
}
|
||||
if res.ResponseStatus != "pending" {
|
||||
return fmt.Errorf("invalid response status: %s", res.ResponseStatus)
|
||||
}
|
||||
c.log.DEBUG.Printf("updated first charge schedule: enable=%v, start=%s, end=%s",
|
||||
enable, stat.EvInfo.Schedules[0].StartTime, stat.EvInfo.Schedules[0].EndTime)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
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()
|
||||
// Helper to set the correct schedule days matching the provided time, to ensure the schedule is only applied for the current day when it's updated
|
||||
func setScheduleDays(schedule *Schedule, t time.Time) {
|
||||
weekday := t.Weekday()
|
||||
schedule.ScheduledDays.Monday = (weekday == time.Monday)
|
||||
schedule.ScheduledDays.Tuesday = (weekday == time.Tuesday)
|
||||
schedule.ScheduledDays.Wednesday = (weekday == time.Wednesday)
|
||||
|
|
@ -97,25 +96,133 @@ func (c *Controller) configureChargeSchedule(schedule *Schedule) {
|
|||
schedule.ScheduledDays.Sunday = (weekday == time.Sunday)
|
||||
}
|
||||
|
||||
func (c *Controller) disableConflictingChargeSchedule(schedule *Schedule) {
|
||||
// configureChargeSchedule configures the provided schedule with the provided start and end time, while ensuring it fits API requirements and avoiding unnecessary.
|
||||
// It returns true if the schedule was changed and false otherwise.
|
||||
func (c *Controller) configureChargeSchedule(schedule *Schedule, start time.Time, end time.Time) bool {
|
||||
const (
|
||||
minTimeInterval = 5 * time.Minute // Minimum time interval accepted by Fiat API in schedules; used for rounding start and end time to avoid API rejections
|
||||
timeFormat = "15:04" // Hours & minutes only
|
||||
defaultEndTime = "23:55" // Default end time to use when enabling charge; this is the last time of the day accepted by the Fiat API
|
||||
fallbackStartTime = "00:00" // Fallback time for schedules crossing midnight; this is the first time of the day accepted by the Fiat API
|
||||
)
|
||||
|
||||
var hasChanged bool // track if we made any change to the schedule to avoid unnecessary API calls
|
||||
|
||||
// Make sure schedule is enabled and of type CHARGE
|
||||
if schedule.ScheduleType != "CHARGE" || !schedule.EnableScheduleType {
|
||||
schedule.ScheduleType = "CHARGE"
|
||||
schedule.EnableScheduleType = true
|
||||
schedule.CabinPriority = false
|
||||
schedule.ChargeToFull = false
|
||||
schedule.RepeatSchedule = true
|
||||
hasChanged = true
|
||||
c.log.DEBUG.Printf("schedule type changed to CHARGE and enabled")
|
||||
}
|
||||
|
||||
// Update start only if provided (non-zero)
|
||||
if !start.IsZero() {
|
||||
// round to 5 minutes to avoid API rejections, and allow trying to start in few minutes in the past to start as soon as possible
|
||||
newStartStr := start.Round(minTimeInterval).Format(timeFormat)
|
||||
|
||||
// Update only if different from current
|
||||
if newStartStr != schedule.StartTime {
|
||||
schedule.StartTime = newStartStr
|
||||
schedule.EndTime = defaultEndTime // Set default end time when enabling charge to avoid API rejections for schedules without end time
|
||||
setScheduleDays(schedule, start) // Set schedule days matching the provided start time to ensure the schedule is only applied for the current day when it's updated
|
||||
hasChanged = true
|
||||
c.log.DEBUG.Printf("set charge schedule start: %s with default end time: %s", schedule.StartTime, schedule.EndTime)
|
||||
} else if schedule.StartTime == schedule.EndTime {
|
||||
// If start and end are the same, it means we previously stop around the same time it starts.
|
||||
// We want to start charge again => we need to set end time to default value to make sure the schedule is enabled
|
||||
schedule.EndTime = defaultEndTime // Set default end time when enabling charge to avoid API rejections for schedules without end time
|
||||
setScheduleDays(schedule, start) // Set schedule days matching the provided start time to ensure the schedule is only applied for the current day when it's updated
|
||||
hasChanged = true
|
||||
c.log.DEBUG.Printf("schedule re-enabled because start and end times were equal")
|
||||
}
|
||||
}
|
||||
|
||||
// Update end only if provided (non-zero)
|
||||
if !end.IsZero() {
|
||||
// round to 5 minutes to avoid API rejections, and allow some delay to stop charge (the round down is the delay)
|
||||
newEndStr := end.Round(minTimeInterval).Format(timeFormat)
|
||||
|
||||
// Update only if different from current
|
||||
if newEndStr != schedule.EndTime {
|
||||
schedule.EndTime = newEndStr
|
||||
hasChanged = true
|
||||
c.log.DEBUG.Printf("set charge schedule end: %s", schedule.EndTime)
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the time changed, make sure the schedule is always consistent even in edge cases.
|
||||
if (!start.IsZero() || !end.IsZero()) && hasChanged {
|
||||
// To ensure proper comparison of times, we need to parse them back from string to time.Time.
|
||||
chkStart, err1 := time.Parse(timeFormat, schedule.StartTime)
|
||||
chkEnd, err2 := time.Parse(timeFormat, schedule.EndTime)
|
||||
if err1 != nil || err2 != nil {
|
||||
c.log.WARN.Printf("failed to parse schedule times: start=%v, end=%v", err1, err2)
|
||||
if err1 != nil {
|
||||
// If start time cannot be parsed, set to fallback value
|
||||
schedule.StartTime = fallbackStartTime
|
||||
setScheduleDays(schedule, end) // Set schedule days matching the provided end time to ensure the schedule is only applied for the current day when it's updated
|
||||
hasChanged = true
|
||||
c.log.DEBUG.Printf("set charge schedule start to fallback value %s due to parse error", fallbackStartTime)
|
||||
}
|
||||
if err2 != nil {
|
||||
// If end time cannot be parsed, set to default end time
|
||||
schedule.EndTime = defaultEndTime
|
||||
hasChanged = true
|
||||
c.log.DEBUG.Printf("set charge schedule end to default value %s due to parse error", defaultEndTime)
|
||||
}
|
||||
} else if chkStart.After(chkEnd) {
|
||||
// If start time is after end time (can only happen when setting end), set start time to fallback value to avoid API rejections for schedules crossing midnight
|
||||
c.log.DEBUG.Printf("start time %s is after end time %s, setting start time to fallback value %s", schedule.StartTime, schedule.EndTime, fallbackStartTime)
|
||||
schedule.StartTime = fallbackStartTime
|
||||
setScheduleDays(schedule, end) // Set schedule days matching the provided end time to ensure the schedule is only applied for the current day when it's updated
|
||||
hasChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
return hasChanged
|
||||
}
|
||||
|
||||
// disableConflictingChargeSchedule makes sure the provided schedule is disabled if it's of type CHARGE to avoid conflicts between schedules and potential API rejections for conflicting schedules. It returns true if the schedule was changed and false otherwise.
|
||||
func (c *Controller) disableConflictingChargeSchedule(schedule *Schedule) bool {
|
||||
// make sure the other charge schedules are disabled in case user changed them
|
||||
if schedule.ScheduleType == "CHARGE" && schedule.EnableScheduleType {
|
||||
schedule.EnableScheduleType = false
|
||||
c.log.DEBUG.Printf("disabled charge schedule other than the first one to avoid conflicts")
|
||||
return true // schedule was changed
|
||||
}
|
||||
return false // schedule was not changed
|
||||
}
|
||||
|
||||
var _ api.Resurrector = (*Controller)(nil)
|
||||
|
||||
func (c *Controller) WakeUp() error {
|
||||
if c.pin == "" {
|
||||
c.log.DEBUG.Printf("pin required for vehicle wakeup")
|
||||
c.log.DEBUG.Printf("vehicle cannot be woken up: no PIN provided")
|
||||
return nil
|
||||
}
|
||||
|
||||
res, err := c.api.ChargeNow(c.vin, c.pin)
|
||||
if err == nil && res.ResponseStatus != "pending" {
|
||||
err = fmt.Errorf("invalid response status: %s", res.ResponseStatus)
|
||||
// get current schedule status from provider (cached)
|
||||
stat, err := c.pvd.statusG()
|
||||
if err == nil && stat.EvInfo != nil && stat.EvInfo.Schedules != nil && len(stat.EvInfo.Schedules) > 0 &&
|
||||
stat.EvInfo.Schedules[0].EnableScheduleType && stat.EvInfo.Schedules[0].ScheduleType == "CHARGE" {
|
||||
// If the first schedule is already enabled for charge, don't go further to avoid chargeNow forcing immediate charge start and messing up with schedules
|
||||
c.log.DEBUG.Printf("vehicle wakeup skipped because charge schedule is already enabled, to avoid conflicts with schedules")
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
// No charge schedule is set and we need to wakeup the vehicle as charge is not starting => let's call ChargeNow to start the charge
|
||||
res, err := c.api.ChargeNow(c.vin, c.pin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("charge now call failed: %w", err)
|
||||
}
|
||||
if res.ResponseStatus != "pending" {
|
||||
return fmt.Errorf("invalid response status: %s", res.ResponseStatus)
|
||||
}
|
||||
c.log.DEBUG.Printf("vehicle wakeup triggered successfully with charge now action")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
259
vehicle/fiat/controller_test.go
Normal file
259
vehicle/fiat/controller_test.go
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
package fiat
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// makeTime constructs a time.Time on an arbitrary fixed date in UTC
|
||||
// using the provided hour and minute. It is used to simulate "now" values
|
||||
// in unit tests without caring about the actual day.
|
||||
func makeTime(t ...int) time.Time {
|
||||
h, m, s := 0, 0, 0
|
||||
if len(t) > 0 {
|
||||
h = t[0]
|
||||
}
|
||||
if len(t) > 1 {
|
||||
m = t[1]
|
||||
}
|
||||
if len(t) > 2 {
|
||||
s = t[2]
|
||||
}
|
||||
return time.Date(2026, 7, 8, h, m, s, 0, time.UTC)
|
||||
}
|
||||
|
||||
func newController() *Controller {
|
||||
return &Controller{log: util.NewLogger("fiat-test")}
|
||||
}
|
||||
|
||||
func TestConfigureChargeSchedule_NominalChargeSession(t *testing.T) {
|
||||
// if the requested end time is before the current end, we should still
|
||||
// honour it immediately (e.g. user shortened the charge window)
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "CHARGE",
|
||||
EnableScheduleType: true,
|
||||
}
|
||||
|
||||
// Start charge
|
||||
has := c.configureChargeSchedule(schedule, makeTime(10, 04), time.Time{})
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "10:05", schedule.StartTime, "start should be rounded to 5-minute")
|
||||
assert.Equal(t, "23:55", schedule.EndTime, "end time should always set to 23:55 when starting charge")
|
||||
assert.True(t, schedule.ScheduledDays.Wednesday, "schedule should be enabled for Wednesday")
|
||||
assert.False(t, schedule.ScheduledDays.Monday || schedule.ScheduledDays.Tuesday || schedule.ScheduledDays.Thursday ||
|
||||
schedule.ScheduledDays.Friday || schedule.ScheduledDays.Saturday || schedule.ScheduledDays.Sunday, "schedule should be false for all other days")
|
||||
|
||||
// Stop charge few hours later on the same day
|
||||
has = c.configureChargeSchedule(schedule, time.Time{}, makeTime(14, 02))
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "10:05", schedule.StartTime, "start should not change when stopping charge")
|
||||
assert.Equal(t, "14:00", schedule.EndTime, "end time should be rounded to 5-minute when stopping charge")
|
||||
assert.True(t, schedule.ScheduledDays.Wednesday, "schedule should be enabled for Wednesday")
|
||||
assert.False(t, schedule.ScheduledDays.Monday || schedule.ScheduledDays.Tuesday || schedule.ScheduledDays.Thursday ||
|
||||
schedule.ScheduledDays.Friday || schedule.ScheduledDays.Saturday || schedule.ScheduledDays.Sunday, "schedule should be false for all other days")
|
||||
}
|
||||
func TestConfigureChargeSchedule_ScheduleTypeEnabling(t *testing.T) {
|
||||
// if schedule type is not CHARGE or is disabled, it should be corrected
|
||||
// and other settings should be initialized
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "PRECONDITIONING",
|
||||
EnableScheduleType: false,
|
||||
StartTime: "10:00",
|
||||
EndTime: "20:00",
|
||||
}
|
||||
|
||||
// Start charge must enable schedule type and set it to CHARGE, and reset other settings to defaults
|
||||
has := c.configureChargeSchedule(schedule, makeTime(19, 42), time.Time{})
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "CHARGE", schedule.ScheduleType, "schedule type should be set to CHARGE")
|
||||
assert.True(t, schedule.EnableScheduleType, "schedule should be enabled")
|
||||
assert.False(t, schedule.CabinPriority, "cabin priority should be false")
|
||||
assert.False(t, schedule.ChargeToFull, "charge to full should be false")
|
||||
assert.True(t, schedule.RepeatSchedule, "repeat schedule should be true")
|
||||
}
|
||||
|
||||
func TestConfigureChargeSchedule_NoChangeWhenNoEndOrStart(t *testing.T) {
|
||||
// if neither start nor end is provided, schedule should not be modified
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "CHARGE",
|
||||
EnableScheduleType: true,
|
||||
StartTime: "10:00",
|
||||
EndTime: "19:40",
|
||||
}
|
||||
|
||||
has := c.configureChargeSchedule(schedule, time.Time{}, time.Time{})
|
||||
assert.False(t, has)
|
||||
assert.Equal(t, "10:00", schedule.StartTime)
|
||||
assert.Equal(t, "19:40", schedule.EndTime)
|
||||
}
|
||||
|
||||
func TestConfigureChargeSchedule_ParseErrorStartOnly(t *testing.T) {
|
||||
// if only the start time fails to parse, it should be set to fallback
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "CHARGE",
|
||||
EnableScheduleType: true,
|
||||
StartTime: "bad_time",
|
||||
EndTime: "23:55",
|
||||
}
|
||||
|
||||
// Set end time with a invalid start time
|
||||
has := c.configureChargeSchedule(schedule, time.Time{}, makeTime(19, 43))
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "00:00", schedule.StartTime)
|
||||
assert.Equal(t, "19:45", schedule.EndTime)
|
||||
}
|
||||
|
||||
func TestConfigureChargeSchedule_ScheduledDaysReset(t *testing.T) {
|
||||
// when schedule is changed, scheduled days should be set to only the
|
||||
// current day to avoid undesired charge in the future
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "CHARGE",
|
||||
EnableScheduleType: true,
|
||||
StartTime: "10:00",
|
||||
EndTime: "19:00",
|
||||
}
|
||||
schedule.ScheduledDays.Monday = false
|
||||
schedule.ScheduledDays.Tuesday = false
|
||||
schedule.ScheduledDays.Wednesday = false
|
||||
schedule.ScheduledDays.Thursday = false
|
||||
schedule.ScheduledDays.Friday = false
|
||||
schedule.ScheduledDays.Saturday = true
|
||||
schedule.ScheduledDays.Sunday = true
|
||||
|
||||
// Friday, 2026-02-27
|
||||
has := c.configureChargeSchedule(schedule, time.Date(2026, 2, 27, 19, 43, 0, 0, time.UTC), time.Time{})
|
||||
assert.True(t, has)
|
||||
// Only Friday should be enabled after start
|
||||
assert.False(t, schedule.ScheduledDays.Monday, "Monday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Tuesday, "Tuesday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Wednesday, "Wednesday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Thursday, "Thursday should be disabled")
|
||||
assert.True(t, schedule.ScheduledDays.Friday, "Friday should be enabled")
|
||||
assert.False(t, schedule.ScheduledDays.Saturday, "Saturday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Sunday, "Sunday should be disabled")
|
||||
|
||||
// Set end time, which should not change the scheduled days as they were already set to current day on start time update
|
||||
has = c.configureChargeSchedule(schedule, time.Time{}, time.Date(2026, 2, 27, 19, 50, 0, 0, time.UTC))
|
||||
assert.True(t, has)
|
||||
assert.False(t, schedule.ScheduledDays.Monday, "Monday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Tuesday, "Tuesday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Wednesday, "Wednesday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Thursday, "Thursday should be disabled")
|
||||
assert.True(t, schedule.ScheduledDays.Friday, "Friday should be enabled")
|
||||
assert.False(t, schedule.ScheduledDays.Saturday, "Saturday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Sunday, "Sunday should be disabled")
|
||||
|
||||
// Next day is Saturday, 2026-02-28: if we start the schedule again on the next day, only Saturday should be enabled
|
||||
has = c.configureChargeSchedule(schedule, time.Date(2026, 2, 28, 8, 12, 0, 0, time.UTC), time.Time{})
|
||||
assert.True(t, has, "expected schedule to be updated for new day")
|
||||
// Only Saturday should be enabled
|
||||
assert.False(t, schedule.ScheduledDays.Monday, "Monday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Tuesday, "Tuesday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Wednesday, "Wednesday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Thursday, "Thursday should be disabled")
|
||||
assert.False(t, schedule.ScheduledDays.Friday, "Friday should be disabled")
|
||||
assert.True(t, schedule.ScheduledDays.Saturday, "Saturday should be enabled")
|
||||
assert.False(t, schedule.ScheduledDays.Sunday, "Sunday should be disabled")
|
||||
}
|
||||
|
||||
func TestConfigureChargeSchedule_CrossingMidnight(t *testing.T) {
|
||||
// if start time is after end time (schedule crossing midnight), start
|
||||
// should be set to the fallback value "00:00" to avoid API rejections
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "CHARGE",
|
||||
EnableScheduleType: true,
|
||||
StartTime: "22:00",
|
||||
EndTime: "23:55",
|
||||
}
|
||||
// Fix weekday for test is a Wednesday, so for this test the previous scheduled day is Tuesday
|
||||
schedule.ScheduledDays.Monday = false
|
||||
schedule.ScheduledDays.Tuesday = true
|
||||
schedule.ScheduledDays.Wednesday = false
|
||||
schedule.ScheduledDays.Thursday = false
|
||||
schedule.ScheduledDays.Friday = false
|
||||
schedule.ScheduledDays.Saturday = false
|
||||
schedule.ScheduledDays.Sunday = false
|
||||
|
||||
// trigger validation by changing the end time (which will be parsed and compared against the start time)
|
||||
now := makeTime(7, 15)
|
||||
has := c.configureChargeSchedule(schedule, time.Time{}, now)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "00:00", schedule.StartTime, "start should be set to fallback when after end")
|
||||
assert.Equal(t, "07:15", schedule.EndTime)
|
||||
assert.False(t, schedule.ScheduledDays.Tuesday, "Tuesday should be disabled")
|
||||
assert.True(t, schedule.ScheduledDays.Wednesday, "Wednesday should be enabled")
|
||||
}
|
||||
|
||||
func TestConfigureChargeSchedule_AvoidEndlessEndPostpone(t *testing.T) {
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "CHARGE",
|
||||
EnableScheduleType: true,
|
||||
StartTime: "10:00",
|
||||
EndTime: "19:40",
|
||||
}
|
||||
|
||||
// when now is only one minute past the original end, end should not be postponed
|
||||
has := c.configureChargeSchedule(schedule, time.Time{}, makeTime(19, 41, 45))
|
||||
assert.False(t, has, "unexpected change for too-soon update")
|
||||
assert.Equal(t, "19:40", schedule.EndTime)
|
||||
|
||||
// once we cross the rouding threshold the schedule should be updated to the next 5‑minute boundary
|
||||
schedule.EndTime = "19:40" // reset for clarity
|
||||
has = c.configureChargeSchedule(schedule, time.Time{}, makeTime(19, 43))
|
||||
assert.True(t, has, "expected schedule to be bumped")
|
||||
assert.Equal(t, "19:45", schedule.EndTime)
|
||||
}
|
||||
|
||||
func TestConfigureChargeSchedule_StartStopStartAgainInShortTime(t *testing.T) {
|
||||
// if start time equals end time, it means charge was stopped right before or right after the schedule start time.
|
||||
// If after this, we want to start charge again, we need to make sure the schedule is correctly re-enabled
|
||||
// by setting end time to default value when enabling charge.
|
||||
c := newController()
|
||||
|
||||
schedule := &Schedule{
|
||||
ScheduleType: "CHARGE",
|
||||
EnableScheduleType: true,
|
||||
}
|
||||
|
||||
// Set both start and end to values that round to the same time (19:05).
|
||||
// First update: set start to 19:03 which rounds to 19:05
|
||||
has := c.configureChargeSchedule(schedule, makeTime(19, 3), time.Time{})
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "19:05", schedule.StartTime)
|
||||
assert.Equal(t, "23:55", schedule.EndTime) // Default end time should always be set when enabling charge
|
||||
|
||||
// Second update: set end to 19:04 which also rounds to 19:05
|
||||
has = c.configureChargeSchedule(schedule, time.Time{}, makeTime(19, 4))
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "19:05", schedule.StartTime)
|
||||
assert.Equal(t, "19:05", schedule.EndTime)
|
||||
|
||||
// Start charge again: few seconds before schedule start time
|
||||
has = c.configureChargeSchedule(schedule, makeTime(19, 4, 30), time.Time{})
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "19:05", schedule.StartTime)
|
||||
assert.Equal(t, "23:55", schedule.EndTime) // Default end time should always be set when enabling charge
|
||||
|
||||
// Start charge again: few seconds after schedule start time
|
||||
has = c.configureChargeSchedule(schedule, makeTime(19, 5, 15), time.Time{})
|
||||
assert.False(t, has, "unexpected change when start again right after schedule start")
|
||||
assert.Equal(t, "19:05", schedule.StartTime)
|
||||
assert.Equal(t, "23:55", schedule.EndTime) // Default end time should always be set when enabling charge
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue