diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs index 7d9a2d4d4..02ac603f5 100644 --- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs +++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs @@ -40,6 +40,7 @@ public class Car public double? Latitude { get; set; } public double? Longitude { get; set; } public CarStateEnum? State { get; set; } + public bool VehicleCommandProtocolRequired { get; set; } public List ChargingProcesses { get; set; } = new List(); } diff --git a/TeslaSolarCharger.Model/Migrations/20240525083523_AddVehicleCommandProtocolRequiredToCar.Designer.cs b/TeslaSolarCharger.Model/Migrations/20240525083523_AddVehicleCommandProtocolRequiredToCar.Designer.cs new file mode 100644 index 000000000..f8c8bf654 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20240525083523_AddVehicleCommandProtocolRequiredToCar.Designer.cs @@ -0,0 +1,714 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TeslaSolarCharger.Model.EntityFramework; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + [DbContext(typeof(TeslaSolarChargerContext))] + [Migration("20240525083523_AddVehicleCommandProtocolRequiredToCar")] + partial class AddVehicleCommandProtocolRequiredToCar + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CachedCarState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CarId") + .HasColumnType("INTEGER"); + + b.Property("CarStateJson") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastUpdated") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CachedCarStates"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChargeMode") + .HasColumnType("INTEGER"); + + b.Property("ChargerActualCurrent") + .HasColumnType("INTEGER"); + + b.Property("ChargerPhases") + .HasColumnType("INTEGER"); + + b.Property("ChargerPilotCurrent") + .HasColumnType("INTEGER"); + + b.Property("ChargerRequestedCurrent") + .HasColumnType("INTEGER"); + + b.Property("ChargerVoltage") + .HasColumnType("INTEGER"); + + b.Property("ChargingPriority") + .HasColumnType("INTEGER"); + + b.Property("ClimateOn") + .HasColumnType("INTEGER"); + + b.Property("IgnoreLatestTimeToReachSocDate") + .HasColumnType("INTEGER"); + + b.Property("LatestTimeToReachSoC") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("MaximumAmpere") + .HasColumnType("INTEGER"); + + b.Property("MinimumAmpere") + .HasColumnType("INTEGER"); + + b.Property("MinimumSoc") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PluggedIn") + .HasColumnType("INTEGER"); + + b.Property("ShouldBeManaged") + .HasColumnType("INTEGER"); + + b.Property("ShouldSetChargeStartTimes") + .HasColumnType("INTEGER"); + + b.Property("SoC") + .HasColumnType("INTEGER"); + + b.Property("SocLimit") + .HasColumnType("INTEGER"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("TeslaFleetApiState") + .HasColumnType("INTEGER"); + + b.Property("TeslaMateCarId") + .HasColumnType("INTEGER"); + + b.Property("UsableEnergy") + .HasColumnType("INTEGER"); + + b.Property("VehicleCommandProtocolRequired") + .HasColumnType("INTEGER"); + + b.Property("Vin") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TeslaMateCarId") + .IsUnique(); + + b.HasIndex("Vin") + .IsUnique(); + + b.ToTable("Cars"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargePrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddSpotPriceToGridPrice") + .HasColumnType("INTEGER"); + + b.Property("EnergyProvider") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(6); + + b.Property("EnergyProviderConfiguration") + .HasColumnType("TEXT"); + + b.Property("GridPrice") + .HasColumnType("TEXT"); + + b.Property("SolarPrice") + .HasColumnType("TEXT"); + + b.Property("SpotPriceCorrectionFactor") + .HasColumnType("TEXT"); + + b.Property("ValidSince") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ChargePrices"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChargingProcessId") + .HasColumnType("INTEGER"); + + b.Property("GridPower") + .HasColumnType("INTEGER"); + + b.Property("SolarPower") + .HasColumnType("INTEGER"); + + b.Property("TimeStamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChargingProcessId"); + + b.ToTable("ChargingDetails"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CarId") + .HasColumnType("INTEGER"); + + b.Property("Cost") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("OldHandledChargeId") + .HasColumnType("INTEGER"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("UsedGridEnergyKwh") + .HasColumnType("TEXT"); + + b.Property("UsedSolarEnergyKwh") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CarId"); + + b.ToTable("ChargingProcesses"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AverageSpotPrice") + .HasColumnType("TEXT"); + + b.Property("CalculatedPrice") + .HasColumnType("TEXT"); + + b.Property("CarId") + .HasColumnType("INTEGER"); + + b.Property("ChargingProcessId") + .HasColumnType("INTEGER"); + + b.Property("UsedGridEnergy") + .HasColumnType("TEXT"); + + b.Property("UsedSolarEnergy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("HandledCharges"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConnectDelayMilliseconds") + .HasColumnType("INTEGER"); + + b.Property("Endianess") + .HasColumnType("INTEGER"); + + b.Property("Host") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ReadTimeoutMilliseconds") + .HasColumnType("INTEGER"); + + b.Property("UnitIdentifier") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ModbusConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusResultConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("INTEGER"); + + b.Property("BitStartIndex") + .HasColumnType("INTEGER"); + + b.Property("CorrectionFactor") + .HasColumnType("TEXT"); + + b.Property("InvertedByModbusResultConfigurationId") + .HasColumnType("INTEGER"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("ModbusConfigurationId") + .HasColumnType("INTEGER"); + + b.Property("Operator") + .HasColumnType("INTEGER"); + + b.Property("RegisterType") + .HasColumnType("INTEGER"); + + b.Property("UsedFor") + .HasColumnType("INTEGER"); + + b.Property("ValueType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InvertedByModbusResultConfigurationId"); + + b.HasIndex("ModbusConfigurationId"); + + b.ToTable("ModbusResultConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Host") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MqttConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttResultConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CorrectionFactor") + .HasColumnType("TEXT"); + + b.Property("MqttConfigurationId") + .HasColumnType("INTEGER"); + + b.Property("NodePattern") + .HasColumnType("TEXT"); + + b.Property("NodePatternType") + .HasColumnType("INTEGER"); + + b.Property("Operator") + .HasColumnType("INTEGER"); + + b.Property("Topic") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UsedFor") + .HasColumnType("INTEGER"); + + b.Property("XmlAttributeHeaderName") + .HasColumnType("TEXT"); + + b.Property("XmlAttributeHeaderValue") + .HasColumnType("TEXT"); + + b.Property("XmlAttributeValueName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MqttConfigurationId"); + + b.ToTable("MqttResultConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.PowerDistribution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChargingPower") + .HasColumnType("INTEGER"); + + b.Property("GridProportion") + .HasColumnType("REAL"); + + b.Property("HandledChargeId") + .HasColumnType("INTEGER"); + + b.Property("PowerFromGrid") + .HasColumnType("INTEGER"); + + b.Property("TimeStamp") + .HasColumnType("TEXT"); + + b.Property("UsedWattHours") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("HandledChargeId"); + + b.ToTable("PowerDistributions"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("HttpMethod") + .HasColumnType("INTEGER"); + + b.Property("NodePatternType") + .HasColumnType("INTEGER"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RestValueConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfigurationHeader", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RestValueConfigurationId") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RestValueConfigurationId", "Key") + .IsUnique(); + + b.ToTable("RestValueConfigurationHeaders"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueResultConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CorrectionFactor") + .HasColumnType("TEXT"); + + b.Property("NodePattern") + .HasColumnType("TEXT"); + + b.Property("Operator") + .HasColumnType("INTEGER"); + + b.Property("RestValueConfigurationId") + .HasColumnType("INTEGER"); + + b.Property("UsedFor") + .HasColumnType("INTEGER"); + + b.Property("XmlAttributeHeaderName") + .HasColumnType("TEXT"); + + b.Property("XmlAttributeHeaderValue") + .HasColumnType("TEXT"); + + b.Property("XmlAttributeValueName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RestValueConfigurationId"); + + b.ToTable("RestValueResultConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.SpotPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SpotPrices"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.TeslaToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAtUtc") + .HasColumnType("TEXT"); + + b.Property("IdToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("INTEGER"); + + b.Property("UnauthorizedCounter") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TeslaTokens"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.TscConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("TscConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingDetail", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", "ChargingProcess") + .WithMany("ChargingDetails") + .HasForeignKey("ChargingProcessId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChargingProcess"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", "Car") + .WithMany("ChargingProcesses") + .HasForeignKey("CarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Car"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusResultConfiguration", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusResultConfiguration", "InvertedByModbusResultConfiguration") + .WithMany() + .HasForeignKey("InvertedByModbusResultConfigurationId"); + + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusConfiguration", "ModbusConfiguration") + .WithMany("ModbusResultConfigurations") + .HasForeignKey("ModbusConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InvertedByModbusResultConfiguration"); + + b.Navigation("ModbusConfiguration"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttResultConfiguration", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttConfiguration", "MqttConfiguration") + .WithMany("MqttResultConfigurations") + .HasForeignKey("MqttConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MqttConfiguration"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.PowerDistribution", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", "HandledCharge") + .WithMany("PowerDistributions") + .HasForeignKey("HandledChargeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HandledCharge"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfigurationHeader", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", "RestValueConfiguration") + .WithMany("Headers") + .HasForeignKey("RestValueConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RestValueConfiguration"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueResultConfiguration", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", "RestValueConfiguration") + .WithMany("RestValueResultConfigurations") + .HasForeignKey("RestValueConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RestValueConfiguration"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", b => + { + b.Navigation("ChargingProcesses"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargingProcess", b => + { + b.Navigation("ChargingDetails"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", b => + { + b.Navigation("PowerDistributions"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ModbusConfiguration", b => + { + b.Navigation("ModbusResultConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.MqttConfiguration", b => + { + b.Navigation("MqttResultConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.RestValueConfiguration", b => + { + b.Navigation("Headers"); + + b.Navigation("RestValueResultConfigurations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/20240525083523_AddVehicleCommandProtocolRequiredToCar.cs b/TeslaSolarCharger.Model/Migrations/20240525083523_AddVehicleCommandProtocolRequiredToCar.cs new file mode 100644 index 000000000..c36e40a2e --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20240525083523_AddVehicleCommandProtocolRequiredToCar.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + /// + public partial class AddVehicleCommandProtocolRequiredToCar : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VehicleCommandProtocolRequired", + table: "Cars", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VehicleCommandProtocolRequired", + table: "Cars"); + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs index aa42293be..d87570600 100644 --- a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs +++ b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class TeslaSolarChargerContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CachedCarState", b => { @@ -122,6 +122,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UsableEnergy") .HasColumnType("INTEGER"); + b.Property("VehicleCommandProtocolRequired") + .HasColumnType("INTEGER"); + b.Property("Vin") .HasColumnType("TEXT"); diff --git a/TeslaSolarCharger.Services/Services/Rest/RestValueExecutionService.cs b/TeslaSolarCharger.Services/Services/Rest/RestValueExecutionService.cs index 531e04c48..19fcd3c2b 100644 --- a/TeslaSolarCharger.Services/Services/Rest/RestValueExecutionService.cs +++ b/TeslaSolarCharger.Services/Services/Rest/RestValueExecutionService.cs @@ -40,7 +40,12 @@ public async Task GetResult(DtoFullRestValueConfiguration config) httpClientHandler.ServerCertificateCustomValidationCallback = MyRemoteCertificateValidationCallback; } using var client = new HttpClient(httpClientHandler); - client.Timeout = TimeSpan.FromSeconds(1); + var timeout = configurationWrapper.PvValueJobUpdateIntervall(); + if (timeout < TimeSpan.FromSeconds(1)) + { + timeout = TimeSpan.FromSeconds(1); + } + client.Timeout = timeout; var request = new HttpRequestMessage(new HttpMethod(config.HttpMethod.ToString()), config.Url); foreach (var header in config.Headers) { diff --git a/TeslaSolarCharger.Tests/Services/Server/TeslaFleetApiService.cs b/TeslaSolarCharger.Tests/Services/Server/TeslaFleetApiService.cs index eb2bebb35..936bc202e 100644 --- a/TeslaSolarCharger.Tests/Services/Server/TeslaFleetApiService.cs +++ b/TeslaSolarCharger.Tests/Services/Server/TeslaFleetApiService.cs @@ -13,14 +13,14 @@ public class TeslaFleetApiService(ITestOutputHelper outputHelper) : TestBase(out [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); + //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/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index 9187a9315..b9398660b 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -146,7 +146,7 @@ else @car.HomeChargePower W - @if (_usingFleetApi == true && _usingFleetApiProxy == true) + @if (_usingFleetApi == true && car.VehicleCommandProtocolRequired) { @if (_testingFleetApiCarIds.Any(i => i == car.CarId)) { @@ -443,7 +443,6 @@ else private int? _apiRequestCount; private string _installationId = ""; private bool? _usingFleetApi; - private bool? _usingFleetApiProxy; private Dictionary _isFleetApiWorkingForCar = new(); private readonly HashSet _testingFleetApiCarIds = new(); @@ -463,8 +462,6 @@ else _isSolarEdgeInstallation = dtoSolarChargerInstallation?.Value; var usingFleetApi = await HttpClient.GetFromJsonAsync>("api/FleetApi/IsFleetApiEnabled").ConfigureAwait(false); _usingFleetApi = usingFleetApi?.Value; - var usingFleetApiProxy = await HttpClient.GetFromJsonAsync>("api/FleetApi/IsFleetApiProxyEnabled").ConfigureAwait(false); - _usingFleetApiProxy = usingFleetApiProxy?.Value; _version = await HttpClient.GetStringAsync("api/Hello/ProductVersion").ConfigureAwait(false); _installationId = await HttpClient.GetStringAsync("api/Hello/GetInstallationId").ConfigureAwait(false); foreach (var carBaseState in _carBaseStates!) diff --git a/TeslaSolarCharger/Server/Controllers/FleetApiController.cs b/TeslaSolarCharger/Server/Controllers/FleetApiController.cs index 408cfdc02..39cbdbb2b 100644 --- a/TeslaSolarCharger/Server/Controllers/FleetApiController.cs +++ b/TeslaSolarCharger/Server/Controllers/FleetApiController.cs @@ -43,5 +43,5 @@ public Task SetChargeLimit(int carId, int percent) [HttpGet] public DtoValue IsFleetApiEnabled() => fleetApiService.IsFleetApiEnabled(); [HttpGet] - public DtoValue IsFleetApiProxyEnabled() => fleetApiService.IsFleetApiProxyEnabled(); + public Task> IsFleetApiProxyEnabled(string vin) => fleetApiService.IsFleetApiProxyEnabled(vin); } diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index d0f7fdcde..5c5b4589d 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -174,16 +174,7 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger logger var pvValueService = webApplication.Services.GetRequiredService(); await pvValueService.ConvertToNewConfiguration().ConfigureAwait(false); - - - - var teslaFleetApiService = webApplication.Services.GetRequiredService(); - var settings = webApplication.Services.GetRequiredService(); - if (await teslaFleetApiService.IsFleetApiProxyNeededInDatabase().ConfigureAwait(false)) - { - settings.FleetApiProxyNeeded = true; - } - + var jobManager = webApplication.Services.GetRequiredService(); //if (!Debugger.IsAttached) { diff --git a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs index d2732a933..1bc198ca2 100644 --- a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs +++ b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs @@ -100,9 +100,10 @@ public async Task> GetCarBaseStatesOfEnabledCars() dtoCarBaseValues.ChargingNotPlannedDueToNoSpotPricesAvailable = await _chargeTimeCalculationService.IsLatestTimeToReachSocAfterLatestKnownChargePrice(enabledCar.Id).ConfigureAwait(false); } - var vin = await _teslamateContext.Cars.Where(c => c.Id == enabledCar.Id).Select(c => c.Vin).FirstOrDefaultAsync().ConfigureAwait(false); - dtoCarBaseValues.FleetApiState = - await _teslaSolarChargerContext.Cars.Where(c => c.TeslaMateCarId == enabledCar.Id).Select(c => c.TeslaFleetApiState).SingleAsync().ConfigureAwait(false); + + var dbCar = await _teslaSolarChargerContext.Cars.Where(c => c.Id == enabledCar.Id).SingleAsync(); + dtoCarBaseValues.FleetApiState = dbCar.TeslaFleetApiState; + dtoCarBaseValues.VehicleCommandProtocolRequired = dbCar.VehicleCommandProtocolRequired; dtoCarBaseValues.ChargeInformation = GenerateChargeInformation(enabledCar); @@ -304,7 +305,7 @@ public List GetChargingSlots(int carId) public async Task UpdateCarFleetApiState(int carId, TeslaCarFleetApiState fleetApiState) { _logger.LogTrace("{method}({carId}, {fleetApiState})", nameof(UpdateCarFleetApiState), carId, fleetApiState); - var car = _teslaSolarChargerContext.Cars.First(c => c.TeslaMateCarId == carId); + var car = _teslaSolarChargerContext.Cars.First(c => c.Id == carId); car.TeslaFleetApiState = fleetApiState; await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } diff --git a/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs index 3097349c9..86aaa769e 100644 --- a/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs @@ -12,8 +12,7 @@ public interface ITeslaFleetApiService Task OpenChargePortDoor(int carId); Task> TestFleetApiAccess(int carId); DtoValue IsFleetApiEnabled(); - DtoValue IsFleetApiProxyEnabled(); - Task IsFleetApiProxyNeededInDatabase(); + Task> IsFleetApiProxyEnabled(string vin); Task RefreshCarData(); Task RefreshTokensIfAllowedAndNeeded(); Task RefreshFleetApiRequestsAreAllowed(); diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 9649bf67a..cb030194c 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -187,7 +187,7 @@ public async Task> TestFleetApiAccess(int carId) await WakeUpCarIfNeeded(carId, inMemoryCar.State).ConfigureAwait(false); var result = await SendCommandToTeslaApi(vin, OpenChargePortDoorRequest, HttpMethod.Post).ConfigureAwait(false); var successResult = result?.Response?.Result == true; - var car = teslaSolarChargerContext.Cars.First(c => c.TeslaMateCarId == carId); + var car = teslaSolarChargerContext.Cars.First(c => c.Id == carId); car.TeslaFleetApiState = successResult ? TeslaCarFleetApiState.Ok : TeslaCarFleetApiState.NotWorking; await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); return new DtoValue(successResult); @@ -208,11 +208,14 @@ public DtoValue IsFleetApiEnabled() return new DtoValue(isEnabled); } - public DtoValue IsFleetApiProxyEnabled() + public async Task> IsFleetApiProxyEnabled(string vin) { logger.LogTrace("{method}", nameof(IsFleetApiProxyEnabled)); - var isEnabled = configurationWrapper.UseFleetApiProxy(); - return new DtoValue(isEnabled); + var fleetApiProxyEnabled = await teslaSolarChargerContext.Cars + .Where(c => c.Vin == vin) + .Select(c => c.VehicleCommandProtocolRequired) + .FirstAsync(); + return new DtoValue(fleetApiProxyEnabled); } public async Task OpenChargePortDoor(int carId) @@ -466,8 +469,8 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState) using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); var content = new StringContent(contentData, System.Text.Encoding.UTF8, "application/json"); - - var baseUrl = GetFleetApiBaseUrl(accessToken.Region, fleetApiRequest.NeedsProxy); + var fleetApiProxyRequired = await IsFleetApiProxyEnabled(vin).ConfigureAwait(false); + var baseUrl = GetFleetApiBaseUrl(accessToken.Region, fleetApiRequest.NeedsProxy, fleetApiProxyRequired.Value); var requestUri = $"{baseUrl}api/1/vehicles/{vin}/{fleetApiRequest.RequestUrl}"; settings.TeslaApiRequestCounter++; var request = new HttpRequestMessage() @@ -504,43 +507,32 @@ await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameo $"Result of command request is false {fleetApiRequest.RequestUrl}, {contentData}. Response string: {responseString}") .ConfigureAwait(false); logger.LogError("Result of command request is false {fleetApiRequest.RequestUrl}, {contentData}. Response string: {responseString}", fleetApiRequest.RequestUrl, contentData, responseString); - await HandleUnsignedCommands(vehicleCommandResult).ConfigureAwait(false); + await HandleUnsignedCommands(vehicleCommandResult, vin).ConfigureAwait(false); } } logger.LogDebug("Response: {responseString}", responseString); return teslaCommandResultResponse; } - internal async Task HandleUnsignedCommands(DtoVehicleCommandResult vehicleCommandResult) + private async Task HandleUnsignedCommands(DtoVehicleCommandResult vehicleCommandResult, string vin) { 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)) + if (!(await IsFleetApiProxyEnabled(vin).ConfigureAwait(false)).Value) { - teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() - { - Key = constants.FleetApiProxyNeeded, - Value = true.ToString(), - }); + var car = teslaSolarChargerContext.Cars.First(c => c.Vin == vin); + car.VehicleCommandProtocolRequired = true; await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } - } } - public async Task IsFleetApiProxyNeededInDatabase() - { - return await teslaSolarChargerContext.TscConfigurations.AnyAsync(c => c.Key == constants.FleetApiProxyNeeded).ConfigureAwait(false); - } - - private string GetFleetApiBaseUrl(TeslaFleetApiRegion region, bool useProxyBaseUrl) + private string GetFleetApiBaseUrl(TeslaFleetApiRegion region, bool useProxyBaseUrl, bool fleetApiProxyRequired) { - if (useProxyBaseUrl && configurationWrapper.UseFleetApiProxy()) + if (useProxyBaseUrl && fleetApiProxyRequired) { var configUrl = configurationWrapper.GetFleetApiBaseUrl(); return configUrl ?? throw new KeyNotFoundException("Could not get Tesla HTTP proxy address"); @@ -791,18 +783,27 @@ private async Task HandleNonSuccessTeslaApiStatusCodes(HttpStatusCode statusCode } else if (statusCode == HttpStatusCode.Forbidden) { - logger.LogError("You did not select all scopes, so TSC can't send commands to your car. Response: {responseString}", responseString); - teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() + if (responseString.Contains("Tesla Vehicle Command Protocol required")) { - Key = constants.TokenMissingScopes, Value = responseString, - }); + var car = teslaSolarChargerContext.Cars.First(c => c.Vin == vin); + car.VehicleCommandProtocolRequired = true; + } + else + { + logger.LogError("You did not select all scopes, so TSC can't send commands to your car. Response: {responseString}", responseString); + teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() + { + Key = constants.TokenMissingScopes, + Value = responseString, + }); + } + } else if (statusCode == HttpStatusCode.InternalServerError && responseString.Contains("vehicle rejected request: your public key has not been paired with the vehicle")) { logger.LogError("Vehicle {vin} is not paired with TSC. Add The public key to the vehicle. Response: {responseString}", vin, responseString); - var teslaMateCarId = teslamateContext.Cars.First(c => c.Vin == vin).Id; - var car = teslaSolarChargerContext.Cars.First(c => c.TeslaMateCarId == teslaMateCarId); + var car = teslaSolarChargerContext.Cars.First(c => c.Vin == vin); car.TeslaFleetApiState = TeslaCarFleetApiState.NotWorking; } else diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index aee65f7bc..dffc64a3b 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -22,6 +22,7 @@ edge + b27b0d7b-1a2e-4df5-a19d-dabef9dee70d diff --git a/TeslaSolarCharger/Server/appsettings.Development.json b/TeslaSolarCharger/Server/appsettings.Development.json index a182330d2..7fefcbd1d 100644 --- a/TeslaSolarCharger/Server/appsettings.Development.json +++ b/TeslaSolarCharger/Server/appsettings.Development.json @@ -35,39 +35,8 @@ ] }, "AllowedHosts": "*", - "CurrentPowerToGridUrl": "http://192.168.1.50:5007/api/ChargingLog/GetAverageGridPowerOfLastXseconds", - //"CurrentPowerToGridJsonPattern": "$.data.value", - //"CurrentPowerToGridXmlPattern": "$.data.value", - //"CurrentInverterPowerJsonPattern": "pattern", - //"CurrentInverterPowerXmlPattern": "pattern", - //"CurrentPowerToGridXmlAttributeHeaderName": "Type", - //"CurrentPowerToGridXmlAttributeHeaderValue": "GridPower", - //"CurrentPowerToGridXmlAttributeValueName": "Value", - //"CurrentInverterPowerAttributeHeaderName": "Type", - //"CurrentInverterPowerAttributeHeaderValue": "GridPower", - //"CurrentInverterPowerAttributeValueName": "Value", - //"CurrentPowerToGridInvertValue": false, - "CurrentInverterPowerUrl": "http://192.168.1.50:5007/api/ChargingLog/GetAverageInverterPowerOfLastXseconds", - "TeslaMateApiBaseUrl": "http://192.168.1.50:8097", - "UpdateIntervalSeconds": 30, - "PvValueUpdateIntervalSeconds": 1, - "CarPriorities": "1|2", - "GeoFence": "asdf", - "MinutesUntilSwitchOn": 5, - "MinutesUntilSwitchOff": 5, - "PowerBuffer": 0, - "MqqtClientId": "TeslaSolarChargerDevelopment", - "MosquitoServer": "192.168.1.50", - "TeslaMateDbServer": "192.168.1.50", - "TeslaMateDbPort": "5433", - "TeslaMateDbDatabaseName": "teslamate", - "TeslaMateDbUser": "teslamate", - "TeslaMateDbPassword": "secret", "AllowCORS": true, "DisplayApiRequestCounter": true, - "UseFleetApi": true, - "UseFleetApiProxy": true, - "GetVehicleDataFromTesla": true, "IgnoreSslErrors": true, "GridPriceProvider": { "EnergyProvider": "Tibber", diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index 58e5640cb..7e7dba1f7 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -92,7 +92,6 @@ public interface IConfigurationWrapper bool IsDevelopmentEnvironment(); string GetAwattarBaseUrl(); string? GetFleetApiBaseUrl(); - bool UseFleetApiProxy(); string RestoreTempDirectory(); string ConfigFileDirectory(); string AutoBackupsZipDirectory(); diff --git a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs index cea757be1..e0f28e2a4 100644 --- a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs +++ b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs @@ -17,7 +17,6 @@ public interface ISettings int TeslaApiRequestCounter { get; set; } bool CrashedOnStartup { get; set; } string? StartupCrashMessage { get; set; } - bool FleetApiProxyNeeded { get; set; } bool AllowUnlimitedFleetApiRequests { get; set; } DateTime LastFleetApiRequestAllowedCheck { get; set; } List Cars { get; set; } diff --git a/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs b/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs index 95ce6865c..a2b10d16f 100644 --- a/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs +++ b/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs @@ -21,6 +21,7 @@ public class DtoCarBaseStates public bool? IsHealthy { get; set; } public bool ChargingNotPlannedDueToNoSpotPricesAvailable { get; set; } public TeslaCarFleetApiState FleetApiState { get; set; } + public bool VehicleCommandProtocolRequired { get; set; } public List ChargeInformation { get; set; } = new(); public CarStateEnum? State { get; set; } public List ChargingSlots { get; set; } = new(); diff --git a/TeslaSolarCharger/Shared/Resources/Constants.cs b/TeslaSolarCharger/Shared/Resources/Constants.cs index c95171406..bfd9e6541 100644 --- a/TeslaSolarCharger/Shared/Resources/Constants.cs +++ b/TeslaSolarCharger/Shared/Resources/Constants.cs @@ -18,7 +18,6 @@ public class Constants : IConstants public string FleetApiTokenRequested => "FleetApiTokenRequested"; public string TokenRefreshUnauthorized => "TokenRefreshUnauthorized"; public string TokenMissingScopes => "TokenMissingScopes"; - public string FleetApiProxyNeeded => "FleetApiProxyNeeded"; public string CarConfigurationsConverted => "CarConfigurationsConverted"; public string HandledChargesCarIdsConverted => "HandledChargesCarIdsConverted"; public string HandledChargesConverted => "HandledChargesConverted"; diff --git a/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs b/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs index f3f09af69..27d148c58 100644 --- a/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs +++ b/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs @@ -17,7 +17,6 @@ public interface IConstants string FleetApiTokenRequested { get; } string TokenMissingScopes { get; } string BackupZipBaseFileName { get; } - string FleetApiProxyNeeded { get; } TimeSpan MaxTokenRequestWaitTime { get; } TimeSpan MinTokenRestLifetime { get; } int MaxTokenUnauthorizedCount { get; } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index 5cbb5b9ff..988d5bd5f 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -129,17 +129,6 @@ public bool UseFleetApi() return value; } - public bool UseFleetApiProxy() - { - if (settings.FleetApiProxyNeeded) - { - return true; - } - var environmentVariableName = "UseFleetApiProxy"; - var value = configuration.GetValue(environmentVariableName); - return value; - } - public bool GetVehicleDataFromTesla() { var environmentVariableName = "GetVehicleDataFromTesla";