diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs index 0e714c52d..30dc5b9f3 100644 --- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs +++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs @@ -28,9 +28,8 @@ public class Car public int? SoC { get; set; } public int? SocLimit { get; set; } - public int? ChargerPhases { get; set; } - public int? ChargerVoltage { get; set; } + public int? ChargerVoltage { get; set; } //NotAvailabel in Fleet Telemetry public int? ChargerActualCurrent { get; set; } public int? ChargerPilotCurrent { get; set; } public int? ChargerRequestedCurrent { get; set; } @@ -39,6 +38,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 DateTime? VehicleRateLimitedUntil { get; set; } public DateTime? VehicleDataRateLimitedUntil { get; set; } @@ -50,6 +50,7 @@ public class Car public int ApiRefreshIntervalSeconds { get; set; } public string? BleApiBaseUrl { get; set; } public bool UseFleetTelemetry { get; set; } + public bool UseFleetTelemetryForLocationData { get; set; } public string? WakeUpCalls { get; set; } public string? VehicleDataCalls { get; set; } diff --git a/TeslaSolarCharger.Model/Migrations/20241027104502_AddUseFleetTelemetryForLocationDataToCars.Designer.cs b/TeslaSolarCharger.Model/Migrations/20241027104502_AddUseFleetTelemetryForLocationDataToCars.Designer.cs new file mode 100644 index 000000000..999433b57 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20241027104502_AddUseFleetTelemetryForLocationDataToCars.Designer.cs @@ -0,0 +1,931 @@ +// +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("20241027104502_AddUseFleetTelemetryForLocationDataToCars")] + partial class AddUseFleetTelemetryForLocationDataToCars + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.BackendNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BackendIssueId") + .HasColumnType("INTEGER"); + + b.Property("DetailText") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Headline") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsConfirmed") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("ValidFromDate") + .HasColumnType("TEXT"); + + b.Property("ValidFromVersion") + .HasColumnType("TEXT"); + + b.Property("ValidToDate") + .HasColumnType("TEXT"); + + b.Property("ValidToVersion") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BackendNotifications"); + }); + + 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("BleApiBaseUrl") + .HasColumnType("TEXT"); + + b.Property("ChargeMode") + .HasColumnType("INTEGER"); + + b.Property("ChargeStartCalls") + .HasColumnType("TEXT"); + + b.Property("ChargeStopCalls") + .HasColumnType("TEXT"); + + 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("ChargingCommandsRateLimitedUntil") + .HasColumnType("TEXT"); + + b.Property("ChargingPriority") + .HasColumnType("INTEGER"); + + b.Property("ClimateOn") + .HasColumnType("INTEGER"); + + b.Property("CommandsRateLimitedUntil") + .HasColumnType("TEXT"); + + b.Property("IgnoreLatestTimeToReachSocDate") + .HasColumnType("INTEGER"); + + b.Property("IgnoreLatestTimeToReachSocDateOnWeekend") + .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("OtherCommandCalls") + .HasColumnType("TEXT"); + + b.Property("PluggedIn") + .HasColumnType("INTEGER"); + + b.Property("SetChargingAmpsCall") + .HasColumnType("TEXT"); + + b.Property("ShouldBeManaged") + .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("UseBleForWakeUp") + .HasColumnType("INTEGER"); + + b.Property("UseFleetTelemetry") + .HasColumnType("INTEGER"); + + b.Property("UseFleetTelemetryForLocationData") + .HasColumnType("INTEGER"); + + b.Property("VehicleCalls") + .HasColumnType("TEXT"); + + b.Property("VehicleCommandProtocolRequired") + .HasColumnType("INTEGER"); + + b.Property("VehicleDataCalls") + .HasColumnType("TEXT"); + + b.Property("VehicleDataRateLimitedUntil") + .HasColumnType("TEXT"); + + b.Property("VehicleRateLimitedUntil") + .HasColumnType("TEXT"); + + b.Property("Vin") + .HasColumnType("TEXT"); + + b.Property("WakeUpCalls") + .HasColumnType("TEXT"); + + b.Property("WakeUpRateLimitedUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TeslaMateCarId") + .IsUnique(); + + b.HasIndex("Vin") + .IsUnique(); + + b.ToTable("Cars"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BooleanValue") + .HasColumnType("INTEGER"); + + b.Property("CarId") + .HasColumnType("INTEGER"); + + b.Property("DoubleValue") + .HasColumnType("REAL"); + + b.Property("IntValue") + .HasColumnType("INTEGER"); + + b.Property("InvalidValue") + .HasColumnType("INTEGER"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("StringValue") + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UnknownValue") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CarId"); + + b.ToTable("CarValueLogs"); + }); + + 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("ChargerVoltage") + .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.LoggedError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DismissedAt") + .HasColumnType("TEXT"); + + b.Property("EndTimeStamp") + .HasColumnType("TEXT"); + + b.Property("FurtherOccurrences") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Headline") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IssueKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MethodName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Source") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StackTrace") + .HasColumnType("TEXT"); + + b.Property("StartTimeStamp") + .HasColumnType("TEXT"); + + b.Property("TelegramNotificationSent") + .HasColumnType("INTEGER"); + + b.Property("TelegramResolvedMessageSent") + .HasColumnType("INTEGER"); + + b.Property("Vin") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("LoggedErrors"); + }); + + 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.CarValueLog", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", "Car") + .WithMany("CarValueLogs") + .HasForeignKey("CarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Car"); + }); + + 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("CarValueLogs"); + + 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/20241027104502_AddUseFleetTelemetryForLocationDataToCars.cs b/TeslaSolarCharger.Model/Migrations/20241027104502_AddUseFleetTelemetryForLocationDataToCars.cs new file mode 100644 index 000000000..891466792 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20241027104502_AddUseFleetTelemetryForLocationDataToCars.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + /// + public partial class AddUseFleetTelemetryForLocationDataToCars : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UseFleetTelemetryForLocationData", + table: "Cars", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UseFleetTelemetryForLocationData", + table: "Cars"); + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs index eb627ab22..cb6bc7429 100644 --- a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs +++ b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs @@ -197,6 +197,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UseFleetTelemetry") .HasColumnType("INTEGER"); + b.Property("UseFleetTelemetryForLocationData") + .HasColumnType("INTEGER"); + b.Property("VehicleCalls") .HasColumnType("TEXT"); diff --git a/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj b/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj index 901ad441a..034499005 100644 --- a/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj +++ b/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj @@ -14,7 +14,7 @@ - + diff --git a/TeslaSolarCharger/Client/Components/HiddenErrorsComponent.razor b/TeslaSolarCharger/Client/Components/HiddenErrorsComponent.razor index 37f84e8ab..64217058c 100644 --- a/TeslaSolarCharger/Client/Components/HiddenErrorsComponent.razor +++ b/TeslaSolarCharger/Client/Components/HiddenErrorsComponent.razor @@ -45,7 +45,7 @@ else NoIcon="true" ContentAlignment="HorizontalAlignment.Left" ShowCloseIcon="false"> -

@(error.Headline + $" occured {error.Occurrences.Count} time(s)")

+

@(error.Headline + $"{(error.HideOccurrenceCount ? string.Empty : $" occured {error.OccurrenceCount} time(s)")}")

Hidden reason: @(error.HideReason == LoggedErrorHideReason.NotEnoughOccurrences ? "Not Enough occurrences" : "Dismissed")
diff --git a/TeslaSolarCharger/Client/Components/LoggedErrorsComponent.razor b/TeslaSolarCharger/Client/Components/LoggedErrorsComponent.razor index c4f4aa68c..61599e9f2 100644 --- a/TeslaSolarCharger/Client/Components/LoggedErrorsComponent.razor +++ b/TeslaSolarCharger/Client/Components/LoggedErrorsComponent.razor @@ -44,7 +44,7 @@ else ContentAlignment="HorizontalAlignment.Left" ShowCloseIcon="true" CloseIconClicked="@(_ => DismissError(error.Id))"> -

@(error.Headline + $" occured {error.Occurrences.Count} time(s)")

+

@(error.Headline + $"{(error.HideOccurrenceCount ? string.Empty : $" occured {error.OccurrenceCount} time(s)")}")

@((MarkupString)error.Message) diff --git a/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor b/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor index d540121fa..a9d77f22e 100644 --- a/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor +++ b/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor @@ -5,164 +5,175 @@ @inject HttpClient HttpClient @inject IConstants Constants +@inject ISnackbar Snackbar @implements IDisposable @if (_pvValues != default) { +
+ + + + @if (GridLocation != default && InverterLocation != default) + { + + } + + @if (GridLocation != default && HomeLocation != default) + { + + } + + @if (InverterLocation != default && HomeLocation != default) + { + + } - - - - @if (GridLocation != default && InverterLocation != default) - { - - } - - @if (GridLocation != default && HomeLocation != default) - { - - } - - @if (InverterLocation != default && HomeLocation != default) - { - - } + + @if (InverterLocation != default && BatteryLocation != default) + { + + } + + @if (HomeLocation != default && BatteryLocation != default) + { + + } + + @if (GridLocation != default && BatteryLocation != default) + { + + } - - @if (InverterLocation != default && BatteryLocation != default) - { - - } - - @if (HomeLocation != default && BatteryLocation != default) - { - - } - - @if (GridLocation != default && BatteryLocation != default) - { - - } + + @if (HomeLocation != default && EvChargerLocation != default) + { + + } - - @if (HomeLocation != default && EvChargerLocation != default) - { - - } + @if (HomeLocation == default) + { + + @if (GridLocation != default && EvChargerLocation != default) + { + + } + + @if (InverterLocation != default && EvChargerLocation != default) + { + + } + + @if (EvChargerLocation != default && BatteryLocation != default) + { + + } + } - @if (HomeLocation == default) - { - - @if (GridLocation != default && EvChargerLocation != default) + + + @foreach (var ball in Balls) { - + + + + } - - @if (InverterLocation != default && EvChargerLocation != default) + + + + @if (GridLocation != default) { - + 0 ? "lightgreen" : "#ff3030"))" + stroke="lightblue" stroke-width="4" /> } - - @if (EvChargerLocation != default && BatteryLocation != default) + + @if (InverterLocation != default) { - + + } + + @if (BatteryLocation != default) + { + 0 ? "lightgreen" : "#ff3030"))" + stroke="lightsalmon" stroke-width="4" /> + } + + @if (HomeLocation != default) + { + + } + + @if (EvChargerLocation != default) + { + } - } - - - - @foreach (var ball in Balls) - { - - - - - } - - - @if (GridLocation != default) - { - 0 ? "lightgreen" : "#ff3030"))" - stroke="lightblue" stroke-width="4" /> - } - - @if (InverterLocation != default) - { - - } - - @if (BatteryLocation != default) - { - 0 ? "lightgreen" : "#ff3030"))" - stroke="lightsalmon" stroke-width="4" /> - } - - @if (HomeLocation != default) - { - - } - - @if (EvChargerLocation != default) - { - - } + + @if (GridLocation != default) + { + +
+ +
@(Math.Abs(_pvValues.GridPower ?? 0)) W
+
+
+ } + @if (InverterLocation != default) + { + +
+ +
@_pvValues.InverterPower W
+
+
+ } + @if (BatteryLocation != default) + { + +
+ +
@Math.Abs(_pvValues.HomeBatteryPower ?? 0) W
+
+
+ } + @if (HomeLocation != default) + { + +
+ +
@CalculateHomePower() W
+
+
+ } + @if (EvChargerLocation != default) + { + +
+ +
@_pvValues.CarCombinedChargingPowerAtHome W
+
+
+ } +
- - @if (GridLocation != default) - { - -
- -
@(Math.Abs(_pvValues.GridPower ?? 0)) W
-
-
- } - @if (InverterLocation != default) - { - -
- -
@_pvValues.InverterPower W
-
-
- } - @if (BatteryLocation != default) - { - -
- -
@Math.Abs(_pvValues.HomeBatteryPower ?? 0) W
-
-
- } - @if (HomeLocation != default) - { - -
- -
@CalculateHomePower() W
-
-
- } - @if (EvChargerLocation != default) - { - -
- -
@_pvValues.CarCombinedChargingPowerAtHome W
-
-
- } -
+
+ if (_pvValues.PowerBuffer != default && _pvValues.PowerBuffer != 0) + { +
+ +
+ } } @@ -187,7 +198,7 @@ _periodicTaskHelper = new(); _periodicTaskHelper.Start(RefreshPvValues, TimeSpan.FromSeconds(5)); } - + private async Task RefreshPvValues() { try @@ -629,4 +640,19 @@ var homePower = _pvValues.InverterPower - _pvValues.GridPower - homeBatteryPower - chargingPower; return homePower; } + + private async Task UpdatePowerBuffer(int? newValue) + { + var response = await HttpClient.GetAsync($"api/BaseConfiguration/UpdatePowerBuffer?powerBuffer={newValue ?? 0}").ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + Snackbar.Add("Power Buffer updated", Severity.Success); + } + else + { + Snackbar.Add("Failed to update Power Buffer", Severity.Error); + } + + } + } \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index 92594a72c..858bb319c 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -153,7 +153,7 @@ else + HelpText="Set values higher than 0 to always have some overage (power to grid). Set values lower than 0 to always consume some power from the grid. Note: The power buffer you see on the home page is replaced with this value after a TSC restart. Do only change this value if you want a permanent change. Frequent Base Configuration changes are not recommended. If you can not see a power buffer value on the homepage, set the power buffer here to 1 as on the homepage it is only displayed if different from zero."> diff --git a/TeslaSolarCharger/Client/Pages/CarSettings.razor b/TeslaSolarCharger/Client/Pages/CarSettings.razor index 4c92a6e4a..90955bcb9 100644 --- a/TeslaSolarCharger/Client/Pages/CarSettings.razor +++ b/TeslaSolarCharger/Client/Pages/CarSettings.razor @@ -64,7 +64,13 @@ else For="() => carBasicConfiguration.UseBleForWakeUp" OnValueChanged="StateHasChanged"/> -@* *@ + + @if(carBasicConfiguration.UseFleetTelemetry) + { + + }

BLE Pairing and test

@@ -216,4 +222,10 @@ else _loadingVins.Remove(vin); } + private void UpdateGetlocationDataViaFleetTelemetry(CarBasicConfiguration carBasicConfiguration, bool newUseFleetTelemetryValue) + { + carBasicConfiguration.UseFleetTelemetryForLocationData = newUseFleetTelemetryValue; + InvokeAsync(() => StateHasChanged()); + } + } \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index 575403375..b2028c45a 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -41,9 +41,7 @@ -
- -
+ @if (_carBaseStates == null || _carBaseSettings == null) diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs index d57833bac..c31d0ce6f 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs @@ -15,6 +15,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = false, ShowErrorAfterOccurrences = 1, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.FleetApiTokenNotRequested, new DtoIssue @@ -23,6 +24,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = false, ShowErrorAfterOccurrences = 1, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.FleetApiTokenUnauthorized, new DtoIssue @@ -47,6 +49,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = false, ShowErrorAfterOccurrences = 1, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.FleetApiTokenNotReceived, new DtoIssue @@ -55,6 +58,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = false, ShowErrorAfterOccurrences = 1, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.FleetApiTokenExpired, new DtoIssue @@ -63,6 +67,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = true, ShowErrorAfterOccurrences = 1, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.FleetApiTokenNoApiRequestsAllowed, new DtoIssue @@ -71,6 +76,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = true, ShowErrorAfterOccurrences = 2, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.CrashedOnStartup, new DtoIssue @@ -79,6 +85,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = true, ShowErrorAfterOccurrences = 1, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.RestartNeeded, new DtoIssue @@ -87,6 +94,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = false, ShowErrorAfterOccurrences = 1, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, { issueKeys.GetVehicle, new DtoIssue @@ -191,6 +199,7 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues IsTelegramEnabled = true, ShowErrorAfterOccurrences = 2, HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, } }, }; diff --git a/TeslaSolarCharger/Server/Services/ChargingService.cs b/TeslaSolarCharger/Server/Services/ChargingService.cs index 2cedc81ad..c1db7e426 100644 --- a/TeslaSolarCharger/Server/Services/ChargingService.cs +++ b/TeslaSolarCharger/Server/Services/ChargingService.cs @@ -202,7 +202,7 @@ public async Task CalculatePowerToControl() { var chargingAtHomeSum = settings.CarsToManage.Select(c => c.ChargingPowerAtHome).Sum(); logger.LogDebug("Using Inverter power {inverterPower} minus chargingPower at home {chargingPowerAtHome} as overage", settings.InverterPower, chargingAtHomeSum); - averagedOverage = settings.InverterPower - chargingAtHomeSum - buffer ?? 0; + averagedOverage = settings.InverterPower.Value - (chargingAtHomeSum ?? 0); } var overage = averagedOverage - buffer; diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index da83b3ff2..587a90b82 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -31,7 +31,8 @@ public class ConfigJsonService( IDateTimeProvider dateTimeProvider, IMapperConfigurationFactory mapperConfigurationFactory, JobManager jobManager, - ITeslaMateDbContextWrapper teslaMateDbContextWrapper) + ITeslaMateDbContextWrapper teslaMateDbContextWrapper, + IFleetTelemetryWebSocketService fleetTelemetryWebSocketService) : IConfigJsonService { private bool CarConfigurationFileExists() @@ -248,6 +249,7 @@ public async Task UpdateCarBasicConfiguration(int carId, CarBasicConfiguration c databaseCar.ApiRefreshIntervalSeconds = carBasicConfiguration.ApiRefreshIntervalSeconds; databaseCar.BleApiBaseUrl = carBasicConfiguration.BleApiBaseUrl; databaseCar.UseFleetTelemetry = carBasicConfiguration.UseFleetTelemetry; + databaseCar.UseFleetTelemetryForLocationData = carBasicConfiguration.UseFleetTelemetryForLocationData; await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); var settingsCar = settings.Cars.First(c => c.Id == carId); settingsCar.Name = carBasicConfiguration.Name; @@ -261,6 +263,7 @@ public async Task UpdateCarBasicConfiguration(int carId, CarBasicConfiguration c settingsCar.UseBle = carBasicConfiguration.UseBle; settingsCar.UseBleForWakeUp = carBasicConfiguration.UseBleForWakeUp; settingsCar.BleApiBaseUrl = carBasicConfiguration.BleApiBaseUrl; + await fleetTelemetryWebSocketService.DisconnectWebSocketsByVin(carBasicConfiguration.Vin); } public Task UpdateCarConfiguration(int carId, DepricatedCarConfiguration carConfiguration) diff --git a/TeslaSolarCharger/Server/Services/Contracts/IFleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/Contracts/IFleetTelemetryWebSocketService.cs index 2d0ac3e66..32ecd978a 100644 --- a/TeslaSolarCharger/Server/Services/Contracts/IFleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/Contracts/IFleetTelemetryWebSocketService.cs @@ -3,4 +3,5 @@ public interface IFleetTelemetryWebSocketService { Task ReconnectWebSocketsForEnabledCars(); + Task DisconnectWebSocketsByVin(string vin); } diff --git a/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs b/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs index 18002f0a6..147a9ee6d 100644 --- a/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs +++ b/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs @@ -40,8 +40,9 @@ public async Task>> GetActiveLoggedErrors() var mappingConfiguration = mapperConfigurationFactory.Create(cfg => { cfg.CreateMap() - .ForMember(d => d.Occurrences, opt => opt.MapFrom(s => new List() { s.StartTimeStamp }.Concat(s.FurtherOccurrences))) + .ForMember(d => d.OccurrenceCount, opt => opt.MapFrom(s => s.FurtherOccurrences.Count() + 1)) .ForMember(d => d.Severity, opt => opt.MapFrom(s => possibleIssues.GetIssueByKey(s.IssueKey).IssueSeverity)) + .ForMember(d => d.HideOccurrenceCount, opt => opt.MapFrom(s => possibleIssues.GetIssueByKey(s.IssueKey).HideOccurrenceCount)) ; }); var mapper = mappingConfiguration.CreateMapper(); @@ -50,7 +51,7 @@ public async Task>> GetActiveLoggedErrors() .Select(e => mapper.Map(e)) .ToList(); - var removedErrorCount = errors.RemoveAll(e => e.Occurrences.Count < possibleIssues.GetIssueByKey(e.IssueKey).ShowErrorAfterOccurrences); + var removedErrorCount = errors.RemoveAll(e => e.OccurrenceCount < possibleIssues.GetIssueByKey(e.IssueKey).ShowErrorAfterOccurrences); logger.LogDebug("{removedErrorsCount} errors removed as did not reach minimum error count", removedErrorCount); return Fin>.Succ(errors); }, @@ -71,8 +72,9 @@ public async Task>> GetHiddenErrors() var mappingConfiguration = mapperConfigurationFactory.Create(cfg => { cfg.CreateMap() - .ForMember(d => d.Occurrences, opt => opt.MapFrom(s => new List() { s.StartTimeStamp }.Concat(s.FurtherOccurrences))) + .ForMember(d => d.OccurrenceCount, opt => opt.MapFrom(s => s.FurtherOccurrences.Count() + 1)) .ForMember(d => d.Severity, opt => opt.MapFrom(s => possibleIssues.GetIssueByKey(s.IssueKey).IssueSeverity)) + .ForMember(d => d.HideOccurrenceCount, opt => opt.MapFrom(s => possibleIssues.GetIssueByKey(s.IssueKey).HideOccurrenceCount)) ; }); var mapper2 = mappingConfiguration.CreateMapper(); diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index c7c542ddb..a947ed28c 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -1,9 +1,7 @@ using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using System.Net.WebSockets; using System.Text; -using System.Threading.Tasks.Sources; using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; using TeslaSolarCharger.Model.EntityFramework; using TeslaSolarCharger.Server.Dtos; @@ -28,18 +26,22 @@ public async Task ReconnectWebSocketsForEnabledCars() logger.LogTrace("{method}", nameof(ReconnectWebSocketsForEnabledCars)); var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); - var vins = await context.Cars - .Where(c => c.UseFleetTelemetry) - .Select(c => c.Vin) + var cars = await context.Cars + .Where(c => c.UseFleetTelemetry && (c.ShouldBeManaged == true)) + .Select(c => new + { + c.Vin, + c.UseFleetTelemetryForLocationData, + }) .ToListAsync(); var bytesToSend = Encoding.UTF8.GetBytes("Heartbeat"); - foreach (var vin in vins) + foreach (var car in cars) { - if (string.IsNullOrEmpty(vin)) + if (string.IsNullOrEmpty(car.Vin)) { continue; } - var existingClient = Clients.FirstOrDefault(c => c.Vin == vin); + var existingClient = Clients.FirstOrDefault(c => c.Vin == car.Vin); if (existingClient != default) { if (existingClient.WebSocketClient.State == WebSocketState.Open) @@ -52,7 +54,7 @@ await existingClient.WebSocketClient.SendAsync(segment, WebSocketMessageType.Tex } catch (Exception ex) { - logger.LogError(ex, "Error sending heartbeat for car {vin}", vin); + logger.LogError(ex, "Error sending heartbeat for car {vin}", car); existingClient.WebSocketClient.Dispose(); Clients.Remove(existingClient); } @@ -64,11 +66,26 @@ await existingClient.WebSocketClient.SendAsync(segment, WebSocketMessageType.Tex Clients.Remove(existingClient); } } - ConnectToFleetTelemetryApi(vin); + ConnectToFleetTelemetryApi(car.Vin, car.UseFleetTelemetryForLocationData); + } + } + + public async Task DisconnectWebSocketsByVin(string vin) + { + logger.LogTrace("{method}({vin})", nameof(DisconnectWebSocketsByVin), vin); + var client = Clients.FirstOrDefault(c => c.Vin == vin); + if (client != default) + { + if (client.WebSocketClient.State == WebSocketState.Open) + { + await client.WebSocketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", new CancellationTokenSource(_heartbeatsendTimeout).Token).ConfigureAwait(false); + } + client.WebSocketClient.Dispose(); + Clients.Remove(client); } } - private async Task ConnectToFleetTelemetryApi(string vin) + private async Task ConnectToFleetTelemetryApi(string vin, bool useFleetTelemetryForLocationData) { logger.LogTrace("{method}({carId})", nameof(ConnectToFleetTelemetryApi), vin); var currentDate = dateTimeProvider.UtcNow(); @@ -83,7 +100,7 @@ private async Task ConnectToFleetTelemetryApi(string vin) logger.LogError("Can not connect to WebSocket: No token found for car {vin}", vin); return; } - var url = configurationWrapper.FleetTelemetryApiUrl() + $"teslaToken={token.AccessToken}®ion={token.Region}&vin={vin}&forceReconfiguration=false"; + var url = configurationWrapper.FleetTelemetryApiUrl() + $"teslaToken={token.AccessToken}®ion={token.Region}&vin={vin}&forceReconfiguration=false&includeLocation={useFleetTelemetryForLocationData}"; using var client = new ClientWebSocket(); try { @@ -143,17 +160,23 @@ private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken { // Decode the received message var jsonMessage = Encoding.UTF8.GetString(buffer, 0, result.Count); - logger.LogTrace("Received message: {message}", jsonMessage); if(jsonMessage == "Heartbeat") { + logger.LogTrace("Received heartbeat: {message}", jsonMessage); continue; } - logger.LogDebug("Received non heartbeat message {string}", jsonMessage); - // Deserialize the JSON message into a C# object var message = DeserializeFleetTelemetryMessage(jsonMessage); if (message != null) { - logger.LogDebug("Saving fleet telemetry message"); + if (configurationWrapper.LogLocationData() || + (message.Type != CarValueType.Latitude && message.Type != CarValueType.Longitude)) + { + logger.LogDebug("Save fleet telemetry message {@message}", message); + } + else + { + logger.LogDebug("Save location message for car {carId}", carId); + } var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var carValueLog = new CarValueLog @@ -172,6 +195,10 @@ private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken context.CarValueLogs.Add(carValueLog); await context.SaveChangesAsync().ConfigureAwait(false); } + else + { + logger.LogWarning("Could not deserialize non heartbeat message {string}", jsonMessage); + } } } catch (Exception ex) diff --git a/TeslaSolarCharger/Server/appsettings.Development.json b/TeslaSolarCharger/Server/appsettings.Development.json index abea56fc1..424d49e0f 100644 --- a/TeslaSolarCharger/Server/appsettings.Development.json +++ b/TeslaSolarCharger/Server/appsettings.Development.json @@ -40,6 +40,7 @@ "ShouldUseFakeSolarValues": true, "IgnoreSslErrors": true, "BleBaseUrl": "http://raspible:7210/", + //"FleetTelemetryApiUrl": "wss://localhost:7281/ws?", "GridPriceProvider": { "EnergyProvider": "Tibber", "Octopus": { diff --git a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs index 66db9673c..248a23626 100644 --- a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs +++ b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs @@ -19,6 +19,7 @@ public CarBasicConfiguration(int id, string? name) } public int Id { get; set; } public string? Name { get; set; } + [Disabled] public string Vin { get; set; } [Range(1, int.MaxValue)] [Postfix("A")] @@ -49,6 +50,9 @@ public CarBasicConfiguration(int id, string? name) public int ApiRefreshIntervalSeconds { get; set; } [HelperText("Needed to send commands via BLE to the car. An example value would be `http://raspible:7210/`")] public string? BleApiBaseUrl { get; set; } - [HelperText("If enabled, some data will be transferred via Fleet Telemetry. This improves the delay in the TSC detection of plugin and out of the car, as well as changes in the charging speed. Note: All data transferred via Fleet Telemetry passes my server.")] + [HelperText("If enabled, some data will be transferred via Fleet Telemetry. This improves the delay in the TSC detection of plugin and out of the car, as well as changes in the charging speed. Note: All data transferred via Fleet Telemetry passes my server. For now, the fleet telemetry fields ModuleTempMin, ModuleTempMax,ChargeAmps, ChargeCurrentRequest, ChargeCurrentRequestMax, DetailedChargeState are requested.")] public bool UseFleetTelemetry { get; set; } + + [HelperText("Enabling this results in additionally streaming the field Location over my server. If disabled and TeslaMate is not selected as DataSource TSC takes up to 8 minutes to detect if the car is at home. If you do not mind that your car location data passes my server, do not disable this option.")] + public bool UseFleetTelemetryForLocationData { get; set; } = true; } diff --git a/TeslaSolarCharger/Shared/Dtos/DtoIssue.cs b/TeslaSolarCharger/Shared/Dtos/DtoIssue.cs index 20a851245..1c5f214f2 100644 --- a/TeslaSolarCharger/Shared/Dtos/DtoIssue.cs +++ b/TeslaSolarCharger/Shared/Dtos/DtoIssue.cs @@ -11,4 +11,5 @@ public class DtoIssue /// If true the issue Starts with the specified issue key and can have multiple variations separated from the main issue key by _ /// public bool HasPlaceHolderIssueKey { get; set; } + public bool HideOccurrenceCount { get; set; } } diff --git a/TeslaSolarCharger/Shared/Dtos/IndexRazor/PvValues/DtoPvValues.cs b/TeslaSolarCharger/Shared/Dtos/IndexRazor/PvValues/DtoPvValues.cs index c23e4fc75..4f59297be 100644 --- a/TeslaSolarCharger/Shared/Dtos/IndexRazor/PvValues/DtoPvValues.cs +++ b/TeslaSolarCharger/Shared/Dtos/IndexRazor/PvValues/DtoPvValues.cs @@ -1,4 +1,6 @@ -namespace TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues; +using TeslaSolarCharger.Shared.Attributes; + +namespace TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues; //Attention: this also is implemented in TeslaSolarCharger.SharedBackend.Dtos. Can not be combined as this would result in UI needing all dependecies of SharedBackend project public class DtoPvValues @@ -7,6 +9,7 @@ public class DtoPvValues public int? GridPower { get; set; } public int? HomeBatteryPower { get; set; } public int? HomeBatterySoc { get; set; } + [Postfix("W")] public int? PowerBuffer { get; set; } public int? CarCombinedChargingPowerAtHome { get; set; } public DateTimeOffset? LastUpdated { get; set; } diff --git a/TeslaSolarCharger/Shared/Dtos/LoggedError/DtoLoggedError.cs b/TeslaSolarCharger/Shared/Dtos/LoggedError/DtoLoggedError.cs index 908a6b04b..bb838029c 100644 --- a/TeslaSolarCharger/Shared/Dtos/LoggedError/DtoLoggedError.cs +++ b/TeslaSolarCharger/Shared/Dtos/LoggedError/DtoLoggedError.cs @@ -8,7 +8,8 @@ public class DtoLoggedError public IssueSeverity Severity { get; set; } public string Headline { get; set; } public string IssueKey { get; set; } - public List Occurrences { get; set; } = new(); + public int OccurrenceCount { get; set; } public string? Vin { get; set; } public string Message { get; set; } + public bool HideOccurrenceCount { get; set; } } diff --git a/TeslaSolarCharger/Shared/Enums/CarValueType.cs b/TeslaSolarCharger/Shared/Enums/CarValueType.cs index 74b1bd83c..327a605fc 100644 --- a/TeslaSolarCharger/Shared/Enums/CarValueType.cs +++ b/TeslaSolarCharger/Shared/Enums/CarValueType.cs @@ -10,5 +10,11 @@ public enum CarValueType ChargeCurrentRequest, ChargePortLatch, DetailedChargeState, + IsPluggedIn, + IsCharging, + ChargerPilotCurrent, + Location, + Longitude, + Latitude, Unknown = 9999, }