diff --git a/Plugins.Modbus/Plugins.Modbus.csproj b/Plugins.Modbus/Plugins.Modbus.csproj index d71e445e2..cdce2cb23 100644 --- a/Plugins.Modbus/Plugins.Modbus.csproj +++ b/Plugins.Modbus/Plugins.Modbus.csproj @@ -11,7 +11,7 @@ - + diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj index d05c6a9bd..1bf763b70 100644 --- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj +++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj @@ -12,7 +12,7 @@ - + diff --git a/Plugins.SolarEdge/Plugins.SolarEdge.csproj b/Plugins.SolarEdge/Plugins.SolarEdge.csproj index f8c5617e0..a045cbfa5 100644 --- a/Plugins.SolarEdge/Plugins.SolarEdge.csproj +++ b/Plugins.SolarEdge/Plugins.SolarEdge.csproj @@ -11,7 +11,7 @@ - + diff --git a/Plugins.Solax/Plugins.Solax.csproj b/Plugins.Solax/Plugins.Solax.csproj index 920a8d7f0..746969dbf 100644 --- a/Plugins.Solax/Plugins.Solax.csproj +++ b/Plugins.Solax/Plugins.Solax.csproj @@ -8,9 +8,9 @@ - + - + diff --git a/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj b/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj index 555bf694c..3a8200064 100644 --- a/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj +++ b/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj @@ -9,12 +9,12 @@ - + - + diff --git a/TeslaSolarCharger.Tests/Services/Server/ChargeTimeCalculationService.cs b/TeslaSolarCharger.Tests/Services/Server/ChargeTimeCalculationService.cs index 97d7671ed..08185524e 100644 --- a/TeslaSolarCharger.Tests/Services/Server/ChargeTimeCalculationService.cs +++ b/TeslaSolarCharger.Tests/Services/Server/ChargeTimeCalculationService.cs @@ -50,7 +50,6 @@ public void Calculates_Correct_Full_Speed_Charge_Durations(int minimumSoc, int? }; var chargeTimeCalculationService = Mock.Create(); - Mock.Mock().Setup(c => c.MinimumSocDifference).Returns(2); var chargeDuration = chargeTimeCalculationService.CalculateTimeToReachMinSocAtFullSpeedCharge(car); var expectedTimeSpan = TimeSpan.FromSeconds(expectedTotalSeconds); diff --git a/TeslaSolarCharger.Tests/Services/Server/TeslaFleetApiService.cs b/TeslaSolarCharger.Tests/Services/Server/TeslaFleetApiService.cs new file mode 100644 index 000000000..eb2bebb35 --- /dev/null +++ b/TeslaSolarCharger.Tests/Services/Server/TeslaFleetApiService.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using TeslaSolarCharger.Server.Dtos.TeslaFleetApi; +using Xunit; +using Xunit.Abstractions; + +namespace TeslaSolarCharger.Tests.Services.Server; + +[SuppressMessage("ReSharper", "UseConfigureAwaitFalse")] +public class TeslaFleetApiService(ITestOutputHelper outputHelper) : TestBase(outputHelper) +{ + [Fact] + public async Task CanHandleUnsignedCommands() + { + var commandResult = JsonConvert.DeserializeObject>("{\"response\":{\"result\":false,\"reason\":\"unsigned_cmds_hardlocked\"}}"); + Assert.NotNull(commandResult?.Response); + var fleetApiService = Mock.Create(); + var fleetApiProxyNeeded = await fleetApiService.IsFleetApiProxyNeededInDatabase(); + Assert.False(fleetApiProxyNeeded); + await fleetApiService.HandleUnsignedCommands(commandResult.Response); + fleetApiProxyNeeded = await fleetApiService.IsFleetApiProxyNeededInDatabase(); + Assert.True(fleetApiProxyNeeded); + + } +} diff --git a/TeslaSolarCharger.Tests/Services/Server/TeslaMateApiService.cs b/TeslaSolarCharger.Tests/Services/Server/TeslaMateApiService.cs index a3ecfbd52..c8b94cf00 100644 --- a/TeslaSolarCharger.Tests/Services/Server/TeslaMateApiService.cs +++ b/TeslaSolarCharger.Tests/Services/Server/TeslaMateApiService.cs @@ -5,13 +5,8 @@ namespace TeslaSolarCharger.Tests.Services.Server; -public class TeslaMateApiService : TestBase +public class TeslaMateApiService(ITestOutputHelper outputHelper) : TestBase(outputHelper) { - public TeslaMateApiService(ITestOutputHelper outputHelper) - : base(outputHelper) - { - } - [Theory] [InlineData(18, null, null, false)] [InlineData(18, null, 19, true)] diff --git a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj index 8d3f2c2ec..854dc3878 100644 --- a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj +++ b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj @@ -18,7 +18,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/TeslaSolarCharger.Tests/TestBase.cs b/TeslaSolarCharger.Tests/TestBase.cs index d7898f2be..bd6746ad9 100644 --- a/TeslaSolarCharger.Tests/TestBase.cs +++ b/TeslaSolarCharger.Tests/TestBase.cs @@ -16,7 +16,9 @@ using TeslaSolarCharger.Server.MappingExtensions; using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.TimeProviding; +using TeslaSolarCharger.SharedBackend.Contracts; using Xunit.Abstractions; +using Constants = TeslaSolarCharger.SharedBackend.Values.Constants; namespace TeslaSolarCharger.Tests; @@ -58,6 +60,7 @@ protected TestBase( _fake = new AutoFake(); _fake.Provide(); + _fake.Provide(); _fake.Provide(new FakeDateTimeProvider(currentFakeTime)); _fake.Provide(configuration); @@ -66,6 +69,7 @@ protected TestBase( { b.Register((_, _) => Context); b.Register((_, _) => _fake.Resolve()); + b.Register((_, _) => _fake.Resolve()); b.Register((_, _) => _fake.Resolve()); b.RegisterType(); //b.Register((_, _) => _fake.Resolve()); diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index 29c8ab838..f17c058ac 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -17,7 +17,7 @@ - + diff --git a/TeslaSolarCharger/Server/Services/ChargingService.cs b/TeslaSolarCharger/Server/Services/ChargingService.cs index e1451843d..e0a91580b 100644 --- a/TeslaSolarCharger/Server/Services/ChargingService.cs +++ b/TeslaSolarCharger/Server/Services/ChargingService.cs @@ -207,6 +207,7 @@ private double GetDistance(double longitude, double latitude, double otherLongit public int CalculateAmpByPowerAndCar(int powerToControl, Car car) { + _logger.LogTrace("{method}({powerToControl}, {carId})", nameof(CalculateAmpByPowerAndCar), powerToControl, car.Id); return Convert.ToInt32(Math.Floor(powerToControl / ((double)(_settings.AverageHomeGridVoltage ?? 230) * car.CarState.ActualPhases))); } @@ -407,7 +408,7 @@ private async Task ChangeCarAmp(Car car, int ampToChange, DtoValue max { _logger.LogDebug("Charging should stop"); //Falls Ausschaltbefehl erst seit Kurzem - if (car.CarState.EarliestSwitchOff > _dateTimeProvider.Now()) + if ((car.CarState.EarliestSwitchOff == default) || (car.CarState.EarliestSwitchOff > _dateTimeProvider.Now())) { _logger.LogDebug("Can not stop charging: earliest Switch Off: {earliestSwitchOff}", car.CarState.EarliestSwitchOff); @@ -533,9 +534,11 @@ private void UpdateShouldStartStopChargingSince(Car car) var actualCurrent = car.CarState.ChargerActualCurrent ?? 0; _logger.LogTrace("Actual current: {actualCurrent}", actualCurrent); //This is needed because sometimes actual current is higher than last set amp, leading to higher calculated amp to set, than actually needed - if (actualCurrent > car.CarState.LastSetAmp) + var lastSetAmp = car.CarState.ChargerRequestedCurrent ?? car.CarState.LastSetAmp; + if (actualCurrent > lastSetAmp) { - actualCurrent = car.CarState.LastSetAmp; + _logger.LogTrace("Actual current {actualCurrent} higher than last set amp {lastSetAmp}. Setting actual current as last set amp.", actualCurrent, lastSetAmp); + actualCurrent = lastSetAmp; } ampToSet += actualCurrent; } diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 69604ca8b..9a131c4c6 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -1,5 +1,6 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using System; using System.Globalization; using System.Net; using System.Net.Http.Headers; @@ -107,9 +108,27 @@ public async Task StopCharging(int carId) public async Task SetAmp(int carId, int amps) { logger.LogTrace("{method}({carId}, {amps})", nameof(SetAmp), carId, amps); + var car = settings.Cars.First(c => c.Id == carId); + if (car.CarState.ChargerRequestedCurrent == amps) + { + logger.LogDebug("Correct charging amp already set."); + return; + } var vin = await GetVinByCarId(carId).ConfigureAwait(false); var commandData = $"{{\"charging_amps\":{amps}}}"; var result = await SendCommandToTeslaApi(vin, SetChargingAmpsRequest, commandData).ConfigureAwait(false); + if (amps < 5 && car.CarState.LastSetAmp >= 5 + || amps >= 5 && car.CarState.LastSetAmp < 5) + { + logger.LogDebug("Double set amp to be able to jump over or below 5A"); + await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false); + result = await SendCommandToTeslaApi(vin, SetChargingAmpsRequest, commandData).ConfigureAwait(false); + } + + if (result?.Response?.Result == true) + { + car.CarState.LastSetAmp = amps; + } } public async Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartTime) @@ -327,29 +346,35 @@ await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameo await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), $"Result of command request is false {fleetApiRequest.RequestUrl}, {contentData}. Response string: {responseString}") .ConfigureAwait(false); - if (string.Equals(vehicleCommandResult.Reason, "unsigned_cmds_hardlocked")) - { - settings.FleetApiProxyNeeded = true; - //remove post after a few versions as only used for debugging - await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), - "FleetAPI proxy needed set to true") - .ConfigureAwait(false); - if (!await IsFleetApiProxyNeededInDatabase().ConfigureAwait(false)) - { - teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() - { - Key = constants.FleetApiProxyNeeded, - Value = true.ToString(), - }); - } - - } + await HandleUnsignedCommands(vehicleCommandResult).ConfigureAwait(false); } } logger.LogDebug("Response: {responseString}", responseString); return teslaCommandResultResponse; } + internal async Task HandleUnsignedCommands(DtoVehicleCommandResult vehicleCommandResult) + { + if (string.Equals(vehicleCommandResult.Reason, "unsigned_cmds_hardlocked")) + { + settings.FleetApiProxyNeeded = true; + //remove post after a few versions as only used for debugging + await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), + "FleetAPI proxy needed set to true") + .ConfigureAwait(false); + if (!await IsFleetApiProxyNeededInDatabase().ConfigureAwait(false)) + { + teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() + { + Key = constants.FleetApiProxyNeeded, + Value = true.ToString(), + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + } + + } + } + public async Task IsFleetApiProxyNeededInDatabase() { return await teslaSolarChargerContext.TscConfigurations.AnyAsync(c => c.Key == constants.FleetApiProxyNeeded).ConfigureAwait(false); diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index ac031ba7c..92f4f511a 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -34,8 +34,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -50,7 +50,7 @@ - + diff --git a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj index 141a2ab5d..464e43d79 100644 --- a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj +++ b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj @@ -17,7 +17,7 @@ - +