diff --git a/Plugins.Modbus/Plugins.Modbus.csproj b/Plugins.Modbus/Plugins.Modbus.csproj
index a6f0fde9e..77ca746aa 100644
--- a/Plugins.Modbus/Plugins.Modbus.csproj
+++ b/Plugins.Modbus/Plugins.Modbus.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj
index d1bd51cb2..a11f03de0 100644
--- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj
+++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/Plugins.SolarEdge/Plugins.SolarEdge.csproj b/Plugins.SolarEdge/Plugins.SolarEdge.csproj
index e8b3eb423..f22cae188 100644
--- a/Plugins.SolarEdge/Plugins.SolarEdge.csproj
+++ b/Plugins.SolarEdge/Plugins.SolarEdge.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/Plugins.Solax/Plugins.Solax.csproj b/Plugins.Solax/Plugins.Solax.csproj
index 5d3cd7725..87f606ff9 100644
--- a/Plugins.Solax/Plugins.Solax.csproj
+++ b/Plugins.Solax/Plugins.Solax.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs
index b72019b18..527d0a3b8 100644
--- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs
+++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs
@@ -43,6 +43,7 @@ public class Car
public bool VehicleCommandProtocolRequired { get; set; }
public DateTime? RateLimitedUntil { get; set; }
public bool UseBle { get; set; }
+ public int ApiRefreshIntervalSeconds { get; set; }
public List ChargingProcesses { get; set; } = new List();
}
diff --git a/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs b/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs
index 546ff1052..e11bbbdb4 100644
--- a/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs
+++ b/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs
@@ -102,6 +102,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity()
.HasIndex(h => new { h.RestValueConfigurationId, h.Key })
.IsUnique();
+
+ modelBuilder.Entity()
+ .Property(c => c.ApiRefreshIntervalSeconds)
+ .HasDefaultValue(500);
}
#pragma warning disable CS8618
diff --git a/TeslaSolarCharger.Model/Migrations/20240615153122_AddApiRefreshIntervalSecondsToCars.Designer.cs b/TeslaSolarCharger.Model/Migrations/20240615153122_AddApiRefreshIntervalSecondsToCars.Designer.cs
new file mode 100644
index 000000000..ca640aa8e
--- /dev/null
+++ b/TeslaSolarCharger.Model/Migrations/20240615153122_AddApiRefreshIntervalSecondsToCars.Designer.cs
@@ -0,0 +1,731 @@
+//
+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("20240615153122_AddApiRefreshIntervalSecondsToCars")]
+ partial class AddApiRefreshIntervalSecondsToCars
+ {
+ ///
+ 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("ApiRefreshIntervalSeconds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(500);
+
+ 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("RateLimitedUntil")
+ .HasColumnType("TEXT");
+
+ 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("UseBle")
+ .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("HomeBatteryPower")
+ .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("UsedHomeBatteryEnergyKwh")
+ .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/20240615153122_AddApiRefreshIntervalSecondsToCars.cs b/TeslaSolarCharger.Model/Migrations/20240615153122_AddApiRefreshIntervalSecondsToCars.cs
new file mode 100644
index 000000000..dbdcd3ae0
--- /dev/null
+++ b/TeslaSolarCharger.Model/Migrations/20240615153122_AddApiRefreshIntervalSecondsToCars.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace TeslaSolarCharger.Model.Migrations
+{
+ ///
+ public partial class AddApiRefreshIntervalSecondsToCars : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "ApiRefreshIntervalSeconds",
+ table: "Cars",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 500);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "ApiRefreshIntervalSeconds",
+ table: "Cars");
+ }
+ }
+}
diff --git a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs
index fccc3024a..f98548602 100644
--- a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs
+++ b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs
@@ -47,6 +47,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
+ b.Property("ApiRefreshIntervalSeconds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(500);
+
b.Property("ChargeMode")
.HasColumnType("INTEGER");
diff --git a/TeslaSolarCharger/Client/Pages/CarSettings.razor b/TeslaSolarCharger/Client/Pages/CarSettings.razor
index e929d8302..f0e19fd76 100644
--- a/TeslaSolarCharger/Client/Pages/CarSettings.razor
+++ b/TeslaSolarCharger/Client/Pages/CarSettings.razor
@@ -27,6 +27,8 @@ else
+
+
diff --git a/TeslaSolarCharger/Server/Services/ChargingService.cs b/TeslaSolarCharger/Server/Services/ChargingService.cs
index 5df91384e..5827baaba 100644
--- a/TeslaSolarCharger/Server/Services/ChargingService.cs
+++ b/TeslaSolarCharger/Server/Services/ChargingService.cs
@@ -74,17 +74,6 @@ public async Task SetNewChargingValues()
LogErrorForCarsWithUnknownSocLimit(_settings.CarsToManage);
- //Set to maximum current so will charge on full speed on auto wakeup
- foreach (var car in _settings.CarsToManage)
- {
- if (car is { IsHomeGeofence: true, State: CarStateEnum.Online }
- && car.ChargerRequestedCurrent != car.MaximumAmpere
- && car.ChargeMode != ChargeMode.DoNothing)
- {
- await _teslaService.SetAmp(car.Id, car.MaximumAmpere).ConfigureAwait(false);
- }
- }
-
var relevantCarIds = GetRelevantCarIds();
_logger.LogDebug("Relevant car ids: {@ids}", relevantCarIds);
@@ -336,6 +325,7 @@ public List GetRelevantCarIds()
private async Task ChangeCarAmp(DtoCar dtoCar, int ampToChange, DtoValue maxAmpIncrease)
{
_logger.LogTrace("{method}({param1}, {param2}, {param3})", nameof(ChangeCarAmp), dtoCar.Id, ampToChange, maxAmpIncrease.Value);
+ var actualCurrent = dtoCar.ChargerActualCurrent;
if (maxAmpIncrease.Value < ampToChange)
{
_logger.LogDebug("Reduce current increase from {ampToChange}A to {maxAmpIncrease}A due to limited combined charging current.",
@@ -343,16 +333,16 @@ private async Task ChangeCarAmp(DtoCar dtoCar, int ampToChange, DtoValue 0 && dtoCar.ChargerRequestedCurrent > dtoCar.ChargerActualCurrent && dtoCar.ChargerActualCurrent > 0)
+ if (ampToChange > 0 && dtoCar.ChargerRequestedCurrent > actualCurrent && actualCurrent > 0)
{
//ampToChange = 0;
_logger.LogWarning("Car does not use full request.");
}
var finalAmpsToSet = (dtoCar.ChargerRequestedCurrent ?? 0) + ampToChange;
- if (dtoCar.ChargerActualCurrent == 0)
+ if (actualCurrent == 0)
{
- finalAmpsToSet = (int)(dtoCar.ChargerActualCurrent + ampToChange);
+ finalAmpsToSet = (int)(actualCurrent + ampToChange);
}
_logger.LogDebug("Amps to set: {amps}", finalAmpsToSet);
@@ -386,7 +376,7 @@ private async Task ChangeCarAmp(DtoCar dtoCar, int ampToChange, DtoValue maxAmpIncrease.Value ? ((dtoCar.ChargerActualCurrent ?? 0) + maxAmpIncrease.Value) : maxAmpPerCar;
+ var ampToSet = (maxAmpPerCar - dtoCar.ChargerRequestedCurrent) > maxAmpIncrease.Value ? ((actualCurrent ?? 0) + maxAmpIncrease.Value) : maxAmpPerCar;
_logger.LogDebug("Set current to {ampToSet} after considering max car Current {maxAmpPerCar} and maxAmpIncrease {maxAmpIncrease}", ampToSet, maxAmpPerCar, maxAmpIncrease.Value);
if (dtoCar.State != CarStateEnum.Charging)
{
@@ -399,12 +389,12 @@ private async Task ChangeCarAmp(DtoCar dtoCar, int ampToChange, DtoValue ChangeCarAmp(DtoCar dtoCar, int ampToChange, DtoValue ChangeCarAmp(DtoCar dtoCar, int ampToChange, DtoValue c.Id == carId);
settingsCar.Name = carBasicConfiguration.Name;
diff --git a/TeslaSolarCharger/Server/Services/TeslaBleService.cs b/TeslaSolarCharger/Server/Services/TeslaBleService.cs
index 60474ee4d..f80429459 100644
--- a/TeslaSolarCharger/Server/Services/TeslaBleService.cs
+++ b/TeslaSolarCharger/Server/Services/TeslaBleService.cs
@@ -54,6 +54,15 @@ public async Task SetAmp(string vin, int amps)
Parameters = new List { amps.ToString() },
};
var result = await SendCommandToBle(request).ConfigureAwait(false);
+
+ var car = settings.Cars.First(c => c.Vin == vin);
+ // Double send if over or under 5 amps as Tesla does not change immedediatly
+ if (car.ChargerRequestedCurrent >= 5 && amps < 5 || car.ChargerRequestedCurrent < 5 && amps >= 5)
+ {
+ await Task.Delay(5000).ConfigureAwait(false);
+ result = await SendCommandToBle(request).ConfigureAwait(false);
+ }
+
return result;
}
diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs
index 95fdb092d..4459ca24f 100644
--- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs
+++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs
@@ -13,6 +13,7 @@
using TeslaSolarCharger.Server.Services.Contracts;
using TeslaSolarCharger.Shared.Contracts;
using TeslaSolarCharger.Shared.Dtos;
+using TeslaSolarCharger.Shared.Dtos.Ble;
using TeslaSolarCharger.Shared.Dtos.Contracts;
using TeslaSolarCharger.Shared.Dtos.Settings;
using TeslaSolarCharger.Shared.Enums;
@@ -102,6 +103,12 @@ public async Task StartCharging(int carId, int startAmp, CarStateEnum? carState)
await SetAmp(carId, startAmp).ConfigureAwait(false);
var result = await SendCommandToTeslaApi(vin, ChargeStartRequest, HttpMethod.Post).ConfigureAwait(false);
+ if (result?.Response?.Result == true)
+ {
+ var car = settings.Cars.First(c => c.Id == carId);
+ car.State = CarStateEnum.Charging;
+ car.ChargerActualCurrent = startAmp;
+ }
}
@@ -112,6 +119,8 @@ public async Task WakeUpCar(int carId)
var result = await SendCommandToTeslaApi(vin, WakeUpRequest, HttpMethod.Post).ConfigureAwait(false);
await teslamateApiService.ResumeLogging(carId).ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false);
+ var car = settings.Cars.First(c => c.Id == carId);
+ car.State = CarStateEnum.Online;
}
public async Task StopCharging(int carId)
@@ -119,6 +128,12 @@ public async Task StopCharging(int carId)
logger.LogTrace("{method}({carId})", nameof(StopCharging), carId);
var vin = GetVinByCarId(carId);
var result = await SendCommandToTeslaApi(vin, ChargeStopRequest, HttpMethod.Post).ConfigureAwait(false);
+ if (result?.Response?.Result == true)
+ {
+ var car = settings.Cars.First(c => c.Id == carId);
+ car.State = CarStateEnum.Online;
+ car.ChargerActualCurrent = 0;
+ }
}
public async Task SetAmp(int carId, int amps)
@@ -134,6 +149,12 @@ public async Task SetAmp(int carId, int amps)
var commandData = $"{{\"charging_amps\":{amps}}}";
var result = await SendCommandToTeslaApi(vin, SetChargingAmpsRequest, HttpMethod.Post, commandData, amps).ConfigureAwait(false);
car.LastSetAmp = amps;
+ if (result?.Response?.Result == true)
+ {
+ car.ChargerRequestedCurrent = amps;
+ car.ChargerActualCurrent = car.State == CarStateEnum.Charging ? amps : 0;
+ }
+
}
public async Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartTime)
@@ -221,50 +242,60 @@ public async Task OpenChargePortDoor(int carId)
public async Task RefreshCarData()
{
logger.LogTrace("{method}()", nameof(RefreshCarData));
- if ((!configurationWrapper.GetVehicleDataFromTesla()) && (!configurationWrapper.GetVehicleDataFromTeslaDebug()))
+ if ((!configurationWrapper.GetVehicleDataFromTesla()))
{
logger.LogDebug("Vehicle Data are coming from TeslaMate. Do not refresh car states via Fleet API");
return;
}
- logger.LogTrace("Actually refreshing car data");
var carIds = settings.CarsToManage.Select(c => c.Id).ToList();
foreach (var carId in carIds)
{
- var vin = GetVinByCarId(carId);
+ var car = settings.Cars.First(c => c.Id == carId);
+ var currentUtcDate = dateTimeProvider.DateTimeOffSetUtcNow();
+ if (car.LastApiDataRefresh.AddSeconds(car.ApiRefreshIntervalSeconds) > currentUtcDate)
+ {
+ logger.LogDebug("Do not refresh car data for car {carId} to prevent rate limits", car.Id);
+ continue;
+ }
try
{
- var vehicle = await SendCommandToTeslaApi(vin, VehicleRequest, HttpMethod.Get).ConfigureAwait(false);
- var vehicleResult = vehicle?.Response;
- logger.LogTrace("Got vehicle {@vehicle}", vehicle);
- if (vehicleResult == default)
- {
- await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(RefreshCarData),
- $"Could not deserialize vehicle: {JsonConvert.SerializeObject(vehicle)}").ConfigureAwait(false);
- logger.LogError("Could not deserialize vehicle for car {carId}: {@vehicle}", carId, vehicle);
- continue;
- }
- var vehicleState = vehicleResult.State;
- if (configurationWrapper.GetVehicleDataFromTesla())
- {
- if (vehicleState == "asleep")
- {
- settings.Cars.First(c => c.Id == carId).State = CarStateEnum.Asleep;
- }
- else if (vehicleState == "offline")
- {
- settings.Cars.First(c => c.Id == carId).State = CarStateEnum.Offline;
- }
- }
-
- if (vehicleState is "asleep" or "offline")
- {
- logger.LogDebug("Do not call current vehicle data as car is {state}", vehicleState);
- continue;
- }
- var vehicleData = await SendCommandToTeslaApi(vin, VehicleDataRequest, HttpMethod.Get)
+ //var vehicle = await SendCommandToTeslaApi(car.Vin, VehicleRequest, HttpMethod.Get).ConfigureAwait(false);
+ //var vehicleResult = vehicle?.Response;
+ //logger.LogTrace("Got vehicle {@vehicle}", vehicle);
+ //if (vehicleResult == default)
+ //{
+ // await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(RefreshCarData),
+ // $"Could not deserialize vehicle: {JsonConvert.SerializeObject(vehicle)}").ConfigureAwait(false);
+ // logger.LogError("Could not deserialize vehicle for car {carId}: {@vehicle}", carId, vehicle);
+ // continue;
+ //}
+ //var vehicleState = vehicleResult.State;
+ //if (configurationWrapper.GetVehicleDataFromTesla())
+ //{
+ // if (vehicleState == "asleep")
+ // {
+ // car.State = CarStateEnum.Asleep;
+ // }
+ // else if (vehicleState == "offline")
+ // {
+ // car.State = CarStateEnum.Offline;
+ // }
+ //}
+
+ //if (vehicleState is "asleep" or "offline")
+ //{
+ // logger.LogDebug("Do not call current vehicle data as car is {state}", vehicleState);
+ // continue;
+ //}
+ var vehicleData = await SendCommandToTeslaApi(car.Vin, VehicleDataRequest, HttpMethod.Get)
.ConfigureAwait(false);
+ car.LastApiDataRefresh = currentUtcDate;
logger.LogTrace("Got vehicleData {@vehicleData}", vehicleData);
var vehicleDataResult = vehicleData?.Response;
+ if (vehicleData?.Error?.Contains("offline") == true)
+ {
+ car.State = CarStateEnum.Offline;
+ }
if (vehicleDataResult == default)
{
await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(RefreshCarData),
@@ -275,7 +306,7 @@ await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameo
if (configurationWrapper.GetVehicleDataFromTesla())
{
- var car = settings.Cars.First(c => c.Id == carId);
+
car.Name = vehicleDataResult.VehicleState.VehicleName;
car.SoC = vehicleDataResult.ChargeState.BatteryLevel;
car.SocLimit = vehicleDataResult.ChargeState.ChargeLimitSoc;
@@ -469,20 +500,49 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState)
var bleAddress = configurationWrapper.BleBaseUrl();
if (!string.IsNullOrEmpty(bleAddress))
{
+ var car = settings.Cars.First(c => c.Vin == vin);
+ var result = new DtoBleResult();
if (fleetApiRequest.RequestUrl == ChargeStartRequest.RequestUrl)
{
- await bleService.StartCharging(vin);
+ result = await bleService.StartCharging(vin);
+ if (result.Success)
+ {
+ car.State = CarStateEnum.Charging;
+ car.ChargerActualCurrent = car.ChargerRequestedCurrent;
+ }
}
else if (fleetApiRequest.RequestUrl == ChargeStopRequest.RequestUrl)
{
- await bleService.StopCharging(vin);
+ result = await bleService.StopCharging(vin);
+ if (result.Success)
+ {
+ car.State = CarStateEnum.Online;
+ car.ChargerActualCurrent = 0;
+ }
}
else if (fleetApiRequest.RequestUrl == SetChargingAmpsRequest.RequestUrl)
{
- await bleService.SetAmp(vin, amp!.Value);
+ result = await bleService.SetAmp(vin, amp!.Value);
+ if (result.Success)
+ {
+ car.ChargerRequestedCurrent = amp!.Value;
+ car.ChargerActualCurrent = car.State == CarStateEnum.Charging ? amp!.Value : 0;
+ }
+ }
+
+ if (typeof(T) == typeof(DtoVehicleCommandResult))
+ {
+ var comamndResult = new DtoGenericTeslaResponse() { };
+ comamndResult.Response = (T)(object) new DtoVehicleCommandResult()
+ {
+ Result = result.Success,
+ Reason = result.Message,
+ };
+ return comamndResult;
}
- return new DtoGenericTeslaResponse(){};
+ return new DtoGenericTeslaResponse();
+
}
}
}
@@ -509,11 +569,6 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState)
};
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- if (configurationWrapper.GetVehicleDataFromTeslaDebug())
- {
- await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi),
- $"Logged Response string: {responseString}").ConfigureAwait(false);
- }
logger.LogTrace("Response status code: {statusCode}", response.StatusCode);
logger.LogTrace("Response string: {responseString}", responseString);
logger.LogTrace("Response headers: {@headers}", response.Headers);
diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
index 7b981f96b..5f5ae17cb 100644
--- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
+++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
@@ -61,7 +61,7 @@
-
+
diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json
index 194fa9555..2850f10bb 100644
--- a/TeslaSolarCharger/Server/appsettings.json
+++ b/TeslaSolarCharger/Server/appsettings.json
@@ -58,7 +58,7 @@
"TeslaMateDbPassword": "secret",
"TeslaMateApiBaseUrl": "http://teslamateapi:8080",
"GeoFence": "Home",
- "DisplayApiRequestCounter": false,
+ "DisplayApiRequestCounter": true,
"IgnoreSslErrors": false,
"UseFleetApi": true,
"FleetApiClientId": "f29f71d6285a-4873-8b6b-80f15854892e",
@@ -66,7 +66,7 @@
"TeslaFleetApiBaseUrl": "https://www.teslasolarcharger.de/teslaproxy/",
"UseFleetApiProxy": false,
"LogLocationData": false,
- "GetVehicleDataFromTesla": false,
+ "GetVehicleDataFromTesla": true,
"GetVehicleDataFromTeslaDebug": false,
"AwattarBaseUrl": "https://api.awattar.de/v1/marketdata",
"BleBaseUrl": null,
diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
index 1323491d3..772ce813b 100644
--- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
+++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
@@ -97,7 +97,6 @@ public interface IConfigurationWrapper
string AutoBackupsZipDirectory();
bool LogLocationData();
bool GetVehicleDataFromTesla();
- bool GetVehicleDataFromTeslaDebug();
int? MaxInverterAcPower();
string? BleBaseUrl();
}
diff --git a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs
index d25125c28..e7f60b29a 100644
--- a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs
+++ b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs
@@ -40,6 +40,11 @@ public CarBasicConfiguration(int id, string? name)
public bool ShouldSetChargeStartTimes { get; set; }
[HelperText("Use BLE communication to go around Tesla rate limits. Note: A BLE device (e.g. Raspberry Pi) with installed TeslaSolarChargerBle Container needs to be near your car.")]
public bool UseBle { get; set; }
+ [HelperText("Limits requests to car as getting values is rate limited.")]
+ [Postfix("s")]
+ [Range(11, int.MaxValue)]
+ public int ApiRefreshIntervalSeconds { get; set; }
+
+
-
}
diff --git a/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs b/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs
index 174dcdb8e..874971028 100644
--- a/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs
+++ b/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs
@@ -86,5 +86,7 @@ private int? ChargingPower
public CarStateEnum? State { get; set; }
public bool? Healthy { get; set; }
public bool ReducedChargeSpeedWarning { get; set; }
+ public DateTimeOffset LastApiDataRefresh { get; set; }
+ public int ApiRefreshIntervalSeconds { get; set; }
public List PlannedChargingSlots { get; set; } = new List();
}