Skip to content

Commit

Permalink
Merge pull request #1142 from pkuehnel/fix/SpotPriceWasShiftedToMoreE…
Browse files Browse the repository at this point in the history
…xprensiveHour

fix(ChargeTimeCalculationService): Do not shift already started charging hours to future
  • Loading branch information
pkuehnel authored Feb 23, 2024
2 parents d0baa36 + 97c85d3 commit bafd751
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 10 deletions.
3 changes: 2 additions & 1 deletion TeslaSolarCharger.Tests/Data/SpotPriceDataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public static TeslaSolarChargerContext InitSpotPrices(this TeslaSolarChargerCont
{
context.SpotPrices.Add(new SpotPrice()
{
StartDate = new DateTime(2023, 1, 22, 17, 0, 0), EndDate = new DateTime(2023, 1, 22, 18, 0, 0), Price = new decimal(0.11)
StartDate = new DateTime(2023, 1, 22, 17, 0, 0),
EndDate = new DateTime(2023, 1, 22, 18, 0, 0), Price = new decimal(0.11)
});
return context;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
using Moq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
using TeslaSolarCharger.Server.Services.ApiServices.Contracts;
using TeslaSolarCharger.Server.Services.Contracts;
using TeslaSolarCharger.Shared.Contracts;
using TeslaSolarCharger.Shared.Dtos.Contracts;
using TeslaSolarCharger.Shared.Dtos.Settings;
using TeslaSolarCharger.Shared.Enums;
using TeslaSolarCharger.SharedBackend.Contracts;
using Xunit;
using Xunit.Abstractions;
using static MudBlazor.FilterOperator;
using Car = TeslaSolarCharger.Shared.Dtos.Settings.Car;
using DateTime = System.DateTime;

namespace TeslaSolarCharger.Tests.Services.Server;

Expand Down Expand Up @@ -236,6 +243,32 @@ public void Does_Concatenate_Charging_Slots_Correctly_Partial_Hour_First()
Assert.Single(concatenatedChargingSlots);
}

//This test is based on log data from private message https://tff-forum.de/t/aw-teslasolarcharger-laden-nach-pv-ueberschuss-mit-beliebiger-wallbox/331033
[Fact]
public async Task Does_Use_Cheapest_Price()
{
var spotpricesJson =
"[\r\n {\r\n \"startDate\": \"2024-02-22T18:00:00\",\r\n \"endDate\": \"2024-02-22T19:00:00\",\r\n \"price\": 0.05242\r\n },\r\n {\r\n \"startDate\": \"2024-02-22T19:00:00\",\r\n \"endDate\": \"2024-02-22T20:00:00\",\r\n \"price\": 0.04245\r\n },\r\n {\r\n \"startDate\": \"2024-02-22T20:00:00\",\r\n \"endDate\": \"2024-02-22T21:00:00\",\r\n \"price\": 0.02448\r\n },\r\n {\r\n \"startDate\": \"2024-02-22T21:00:00\",\r\n \"endDate\": \"2024-02-22T22:00:00\",\r\n \"price\": 0.01206\r\n },\r\n {\r\n \"startDate\": \"2024-02-22T22:00:00\",\r\n \"endDate\": \"2024-02-22T23:00:00\",\r\n \"price\": 0.00191\r\n },\r\n {\r\n \"startDate\": \"2024-02-22T23:00:00\",\r\n \"endDate\": \"2024-02-23T00:00:00\",\r\n \"price\": 0.00923\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T00:00:00\",\r\n \"endDate\": \"2024-02-23T01:00:00\",\r\n \"price\": 0.00107\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T01:00:00\",\r\n \"endDate\": \"2024-02-23T02:00:00\",\r\n \"price\": 0.00119\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T02:00:00\",\r\n \"endDate\": \"2024-02-23T03:00:00\",\r\n \"price\": 0.00009\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T03:00:00\",\r\n \"endDate\": \"2024-02-23T04:00:00\",\r\n \"price\": 0.00002\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T04:00:00\",\r\n \"endDate\": \"2024-02-23T05:00:00\",\r\n \"price\": 0.00009\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T05:00:00\",\r\n \"endDate\": \"2024-02-23T06:00:00\",\r\n \"price\": 0.03968\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T06:00:00\",\r\n \"endDate\": \"2024-02-23T07:00:00\",\r\n \"price\": 0.05706\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T07:00:00\",\r\n \"endDate\": \"2024-02-23T08:00:00\",\r\n \"price\": 0.05935\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T08:00:00\",\r\n \"endDate\": \"2024-02-23T09:00:00\",\r\n \"price\": 0.05169\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T09:00:00\",\r\n \"endDate\": \"2024-02-23T10:00:00\",\r\n \"price\": 0.04664\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T10:00:00\",\r\n \"endDate\": \"2024-02-23T11:00:00\",\r\n \"price\": 0.04165\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T11:00:00\",\r\n \"endDate\": \"2024-02-23T12:00:00\",\r\n \"price\": 0.0371\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T12:00:00\",\r\n \"endDate\": \"2024-02-23T13:00:00\",\r\n \"price\": 0.0336\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T13:00:00\",\r\n \"endDate\": \"2024-02-23T14:00:00\",\r\n \"price\": 0.03908\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T14:00:00\",\r\n \"endDate\": \"2024-02-23T15:00:00\",\r\n \"price\": 0.04951\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T15:00:00\",\r\n \"endDate\": \"2024-02-23T16:00:00\",\r\n \"price\": 0.06308\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T16:00:00\",\r\n \"endDate\": \"2024-02-23T17:00:00\",\r\n \"price\": 0.0738\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T17:00:00\",\r\n \"endDate\": \"2024-02-23T18:00:00\",\r\n \"price\": 0.08644\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T18:00:00\",\r\n \"endDate\": \"2024-02-23T19:00:00\",\r\n \"price\": 0.08401\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T19:00:00\",\r\n \"endDate\": \"2024-02-23T20:00:00\",\r\n \"price\": 0.07297\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T20:00:00\",\r\n \"endDate\": \"2024-02-23T21:00:00\",\r\n \"price\": 0.06926\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T21:00:00\",\r\n \"endDate\": \"2024-02-23T22:00:00\",\r\n \"price\": 0.06798\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T22:00:00\",\r\n \"endDate\": \"2024-02-23T23:00:00\",\r\n \"price\": 0.0651\r\n },\r\n {\r\n \"startDate\": \"2024-02-23T23:00:00\",\r\n \"endDate\": \"2024-02-24T00:00:00\",\r\n \"price\": 0.06647\r\n },\r\n {\r\n \"startDate\": \"2024-02-24T00:00:00\",\r\n \"endDate\": \"2024-02-24T01:00:00\",\r\n \"price\": 0.0639\r\n },\r\n {\r\n \"startDate\": \"2024-02-24T01:00:00\",\r\n \"endDate\": \"2024-02-24T02:00:00\",\r\n \"price\": 0.0595\r\n }\r\n]";
var spotPricesToAddToDb = JsonConvert.DeserializeObject<List<SpotPrice>>(spotpricesJson);
Assert.NotNull(spotPricesToAddToDb);
Context.SpotPrices.AddRange(spotPricesToAddToDb);
await Context.SaveChangesAsync();
var chargeTimeCalculationService = Mock.Create<TeslaSolarCharger.Server.Services.ChargeTimeCalculationService>();
var carJson =
"{\"Id\":1,\"Vin\":\"LRW3E7FS2NC\",\"CarConfiguration\":{\"ChargeMode\":3,\"MinimumSoC\":80,\"LatestTimeToReachSoC\":\"2024-02-23T15:30:00\",\"IgnoreLatestTimeToReachSocDate\":false,\"MaximumAmpere\":16,\"MinimumAmpere\":1,\"UsableEnergy\":58,\"ShouldBeManaged\":true,\"ShouldSetChargeStartTimes\":true,\"ChargingPriority\":1},\"CarState\":{\"Name\":\"Model 3\",\"ShouldStartChargingSince\":null,\"EarliestSwitchOn\":null,\"ShouldStopChargingSince\":\"2024-02-22T13:01:37.0448677+01:00\",\"EarliestSwitchOff\":\"2024-02-22T13:06:37.0448677+01:00\",\"ScheduledChargingStartTime\":\"2024-02-24T01:45:00+00:00\",\"SoC\":58,\"SocLimit\":100,\"IsHomeGeofence\":true,\"TimeUntilFullCharge\":\"02:45:00\",\"ReachingMinSocAtFullSpeedCharge\":\"2024-02-23T06:09:34.4100825+01:00\",\"AutoFullSpeedCharge\":true,\"LastSetAmp\":16,\"ChargerPhases\":2,\"ActualPhases\":3,\"ChargerVoltage\":228,\"ChargerActualCurrent\":16,\"ChargerPilotCurrent\":16,\"ChargerRequestedCurrent\":16,\"PluggedIn\":true,\"ClimateOn\":false,\"DistanceToHomeGeofence\":-19,\"ChargingPowerAtHome\":10944,\"State\":3,\"Healthy\":true,\"ReducedChargeSpeedWarning\":false,\"PlannedChargingSlots\":[{\"ChargeStart\":\"2024-02-23T12:00:00+00:00\",\"ChargeEnd\":\"2024-02-23T12:09:34.4150924+00:00\",\"IsActive\":false,\"ChargeDuration\":\"00:09:34.4150924\"},{\"ChargeStart\":\"2024-02-23T02:43:07.0475086+01:00\",\"ChargeEnd\":\"2024-02-23T06:00:00+01:00\",\"IsActive\":true,\"ChargeDuration\":\"03:16:52.9524914\"}]}}";
var car = JsonConvert.DeserializeObject<Shared.Dtos.Settings.Car>(carJson);
Assert.NotNull(car);
Mock.Mock<ISettings>().Setup(ds => ds.Cars).Returns(new List<Car>() { car });
var dateTimeOffsetNow = new DateTimeOffset(2024, 2, 23, 5, 0, 1, TimeSpan.FromHours(1));
Mock.Mock<IDateTimeProvider>().Setup(ds => ds.DateTimeOffSetNow()).Returns(dateTimeOffsetNow);
Mock.Mock<ISpotPriceService>()
.Setup(ds => ds.LatestKnownSpotPriceTime())
.Returns(Task.FromResult(new DateTimeOffset(spotPricesToAddToDb.OrderByDescending(p => p.EndDate).Select(p => p.EndDate).First(), TimeSpan.Zero)));
var chargingSlots = await chargeTimeCalculationService.GenerateSpotPriceChargingSlots(car,
TimeSpan.FromMinutes(69), dateTimeOffsetNow,
new DateTimeOffset(car.CarConfiguration.LatestTimeToReachSoC, TimeSpan.FromHours(1)));
}

[Fact]
public void Does_Concatenate_Charging_Slots_Correctly_Partial_Hour_Last()
{
Expand Down
33 changes: 26 additions & 7 deletions TeslaSolarCharger.Tests/Services/Server/ChargingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ public ChargingService(ITestOutputHelper outputHelper)
}

[Theory, MemberData(nameof(AutoFullSpeedChargeData))]
public void Does_autoenable_fullspeed_charge_if_needed(DtoChargingSlot chargingSlot, bool shouldEnableFullSpeedCharge)
public void Does_autoenable_fullspeed_charge_if_needed(DtoChargingSlot chargingSlot, DateTimeOffset currentDate, bool shouldEnableFullSpeedCharge)
{
Mock.Mock<IDateTimeProvider>()
.Setup(d => d.DateTimeOffSetNow())
.Returns(new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero));
.Returns(currentDate);
var car = new Car()
{
CarState = new CarState()
Expand All @@ -46,11 +46,11 @@ public void Does_autoenable_fullspeed_charge_if_needed(DtoChargingSlot chargingS
}

[Theory, MemberData(nameof(AutoFullSpeedChargeData))]
public void Does_autodisable_fullspeed_charge_if_needed(DtoChargingSlot chargingSlot, bool shouldEnableFullSpeedCharge)
public void Does_autodisable_fullspeed_charge_if_needed(DtoChargingSlot chargingSlot, DateTimeOffset currentDate, bool shouldEnableFullSpeedCharge)
{
Mock.Mock<IDateTimeProvider>()
.Setup(d => d.DateTimeOffSetNow())
.Returns(new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero));
.Returns(currentDate);
var car = new Car()
{
CarState = new CarState()
Expand All @@ -71,9 +71,28 @@ public void Does_autodisable_fullspeed_charge_if_needed(DtoChargingSlot charging

public static readonly object[][] AutoFullSpeedChargeData =
{
new object[] { new DtoChargingSlot() {ChargeStart = new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero), ChargeEnd = new DateTimeOffset(2023, 2, 1, 11, 0, 0, TimeSpan.Zero) }, true },
new object[] { new DtoChargingSlot() {ChargeStart = new DateTimeOffset(2023, 2, 1, 10, 0, 1, TimeSpan.Zero), ChargeEnd = new DateTimeOffset(2023, 2, 1, 11, 0, 0, TimeSpan.Zero) }, false },
new object[] { new DtoChargingSlot() {ChargeStart = new DateTimeOffset(2023, 2, 1, 8, 0, 1, TimeSpan.Zero), ChargeEnd = new DateTimeOffset(2023, 2, 1, 9, 0, 0, TimeSpan.Zero) }, false },
new object[] { new DtoChargingSlot() {ChargeStart = new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero), ChargeEnd = new DateTimeOffset(2023, 2, 1, 11, 0, 0, TimeSpan.Zero) }, new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero), true },
new object[] { new DtoChargingSlot() {ChargeStart = new DateTimeOffset(2023, 2, 1, 10, 0, 1, TimeSpan.Zero), ChargeEnd = new DateTimeOffset(2023, 2, 1, 11, 0, 0, TimeSpan.Zero) }, new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero), false },
new object[] { new DtoChargingSlot() {ChargeStart = new DateTimeOffset(2023, 2, 1, 8, 0, 1, TimeSpan.Zero), ChargeEnd = new DateTimeOffset(2023, 2, 1, 9, 0, 0, TimeSpan.Zero) }, new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero), false },
new object[] {
new DtoChargingSlot()
{
ChargeStart = new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero),
ChargeEnd = new DateTimeOffset(2023, 2, 1, 11, 0, 0, TimeSpan.Zero),
},
new DateTimeOffset(2023, 2, 1, 11, 1, 0, TimeSpan.FromHours(1)),
true,
},
new object[] {
new DtoChargingSlot()
{
ChargeStart = new DateTimeOffset(2023, 2, 1, 10, 0, 0, TimeSpan.Zero),
ChargeEnd = new DateTimeOffset(2023, 2, 1, 11, 0, 0, TimeSpan.Zero),
},
new DateTimeOffset(2023, 2, 1, 10, 59, 0, TimeSpan.FromHours(1)),
false,
},

};

[Theory]
Expand Down
1 change: 1 addition & 0 deletions TeslaSolarCharger.Tests/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using TeslaSolarCharger.Shared.Contracts;
using TeslaSolarCharger.Shared.TimeProviding;
using TeslaSolarCharger.SharedBackend.Contracts;
using TeslaSolarCharger.Tests.Data;
using Xunit.Abstractions;
using Constants = TeslaSolarCharger.SharedBackend.Values.Constants;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,9 @@ internal List<DtoChargingSlot> ReduceNumberOfSpotPricedChargingSessions(List<Dto
{
var lastChargingSlot = chargingSlots[i - 1];
var currenChargingSlot = chargingSlots[i];
//Only move charging slots shorter than one hour as otherwise chargetime more hours ago could be reduced and shifted to more expensive hours
if (lastChargingSlot.ChargeDuration < TimeSpan.FromHours(1)
//Only move not started charging slots shorter than one hour as otherwise chargetime more hours ago could be reduced and shifted to more expensive hours
if ((!(lastChargingSlot.ChargeStart < dateTimeProvider.DateTimeOffSetNow()))
&& lastChargingSlot.ChargeDuration < TimeSpan.FromHours(1)
&& (lastChargingSlot.ChargeEnd - currenChargingSlot.ChargeStart).TotalHours < 1)
{
lastChargingSlot.ChargeStart = currenChargingSlot.ChargeStart - lastChargingSlot.ChargeDuration;
Expand Down

0 comments on commit bafd751

Please sign in to comment.