From 358c5f25c26da73e3c717b0662943ae0c13aaf52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 24 Feb 2025 15:34:18 +0100 Subject: [PATCH 1/4] feat(CarSettings): can select homedetection via --- .../Entities/TeslaSolarCharger/Car.cs | 1 + ...0224141002_AddHomeDetectionVia.Designer.cs | 914 ++++++++++++++++++ .../20250224141002_AddHomeDetectionVia.cs | 29 + .../TeslaSolarChargerContextModelSnapshot.cs | 3 + .../Client/Pages/CarSettings.razor | 98 +- .../Server/Services/ConfigJsonService.cs | 2 + .../Shared/Dtos/CarBasicConfiguration.cs | 2 + .../Shared/Enums/HomeDetectionVia.cs | 9 + TeslaSolarCharger/Shared/Extensions.cs | 13 + 9 files changed, 1049 insertions(+), 22 deletions(-) create mode 100644 TeslaSolarCharger.Model/Migrations/20250224141002_AddHomeDetectionVia.Designer.cs create mode 100644 TeslaSolarCharger.Model/Migrations/20250224141002_AddHomeDetectionVia.cs create mode 100644 TeslaSolarCharger/Shared/Enums/HomeDetectionVia.cs diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs index 7a3833203..bd8eb893d 100644 --- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs +++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs @@ -46,6 +46,7 @@ public class Car public bool UseFleetTelemetry { get; set; } public bool IncludeTrackingRelevantFields { get; set; } public bool IsAvailableInTeslaAccount { get; set; } + public HomeDetectionVia HomeDetectionVia { get; set; } public List WakeUpCalls { get; set; } = new(); public List VehicleDataCalls { get; set; } = new(); diff --git a/TeslaSolarCharger.Model/Migrations/20250224141002_AddHomeDetectionVia.Designer.cs b/TeslaSolarCharger.Model/Migrations/20250224141002_AddHomeDetectionVia.Designer.cs new file mode 100644 index 000000000..ccae2dce1 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20250224141002_AddHomeDetectionVia.Designer.cs @@ -0,0 +1,914 @@ +// +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("20250224141002_AddHomeDetectionVia")] + partial class AddHomeDetectionVia + { + /// + 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.BackendToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAtUtc") + .HasColumnType("TEXT"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BackendTokens"); + }); + + 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("BleApiBaseUrl") + .HasColumnType("TEXT"); + + b.Property("ChargeMode") + .HasColumnType("INTEGER"); + + b.Property("ChargeStartCalls") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ChargeStopCalls") + .IsRequired() + .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("ChargingPriority") + .HasColumnType("INTEGER"); + + b.Property("ClimateOn") + .HasColumnType("INTEGER"); + + b.Property("HomeDetectionVia") + .HasColumnType("INTEGER"); + + b.Property("IgnoreLatestTimeToReachSocDate") + .HasColumnType("INTEGER"); + + b.Property("IgnoreLatestTimeToReachSocDateOnWeekend") + .HasColumnType("INTEGER"); + + b.Property("IncludeTrackingRelevantFields") + .HasColumnType("INTEGER"); + + b.Property("IsAvailableInTeslaAccount") + .HasColumnType("INTEGER"); + + b.Property("IsFleetTelemetryHardwareIncompatible") + .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") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PluggedIn") + .HasColumnType("INTEGER"); + + b.Property("SetChargingAmpsCall") + .IsRequired() + .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("UseFleetTelemetry") + .HasColumnType("INTEGER"); + + b.Property("VehicleCalls") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VehicleCommandProtocolRequired") + .HasColumnType("INTEGER"); + + b.Property("VehicleDataCalls") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Vin") + .HasColumnType("TEXT"); + + b.Property("WakeUpCalls") + .IsRequired() + .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.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/20250224141002_AddHomeDetectionVia.cs b/TeslaSolarCharger.Model/Migrations/20250224141002_AddHomeDetectionVia.cs new file mode 100644 index 000000000..a52c6c184 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20250224141002_AddHomeDetectionVia.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + /// + public partial class AddHomeDetectionVia : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "HomeDetectionVia", + table: "Cars", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "HomeDetectionVia", + table: "Cars"); + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs index 08fee2e39..92c6e76d1 100644 --- a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs +++ b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs @@ -144,6 +144,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ClimateOn") .HasColumnType("INTEGER"); + b.Property("HomeDetectionVia") + .HasColumnType("INTEGER"); + b.Property("IgnoreLatestTimeToReachSocDate") .HasColumnType("INTEGER"); diff --git a/TeslaSolarCharger/Client/Pages/CarSettings.razor b/TeslaSolarCharger/Client/Pages/CarSettings.razor index 8690cd421..d57e5b96c 100644 --- a/TeslaSolarCharger/Client/Pages/CarSettings.razor +++ b/TeslaSolarCharger/Client/Pages/CarSettings.razor @@ -4,10 +4,16 @@ @using TeslaSolarCharger.Shared.Dtos.Ble @using Newtonsoft.Json @using TeslaSolarCharger.Client.Helper.Contracts +@using TeslaSolarCharger.Shared @using TeslaSolarCharger.Shared.Enums +@using TeslaSolarCharger.Shared.Helper.Contracts +@using TeslaSolarCharger.Shared.Resources.Contracts + @inject HttpClient HttpClient @inject IHttpClientHelper HttpClientHelper @inject ISnackbar Snackbar +@inject IConstants Constants +@inject IStringHelper StringHelper Car Settings

Car Settings

@@ -16,8 +22,8 @@ @if (_fleetApiTokenState != null && _fleetApiTokenState != TokenState.UpToDate) { + NoIcon="true" + ContentAlignment="HorizontalAlignment.Left">

Create Token.

Go to Cloud Connection, Generate a Tesla Fleet API Token and restart TSC to see cars here.
@@ -25,8 +31,8 @@ @if (_fleetApiTokenState == TokenState.UpToDate) { + NoIcon="true" + ContentAlignment="HorizontalAlignment.Left">
Restart TSC to add new cars
If you do not see all cars here that are available in your Tesla account, restart TSC.
@@ -44,13 +50,13 @@ else {
+ OnAfterSuccessfullSubmit="item => UpdateShouldDisplayBleTest(item)" + SubmitUrl="@($"api/Config/UpdateCarBasicConfiguration?carId={carBasicConfiguration.Item.Id}")"> + For="() => carBasicConfiguration.Item.ShouldBeManaged" + OnValueChanged="_ => InvokeAsync(() => StateHasChanged())" /> @if (carBasicConfiguration.Item.ShouldBeManaged) { @@ -58,29 +64,71 @@ else + For="() => carBasicConfiguration.Item.UseBle" + OnValueChanged="_ => carBasicConfiguration.ClearErrors(nameof(carBasicConfiguration.Item.BleApiBaseUrl))" /> @if (carBasicConfiguration.Item.UseBle) { } - - @if (carBasicConfiguration.Item.UseFleetTelemetry && !carBasicConfiguration.Item.IncludeTrackingRelevantFields) + @switch (carBasicConfiguration.Item.HomeDetectionVia) { - -
Home Address of Tesla is used to determine if car is at home
- As tracking relevant fields are not included, the home address set in the car is used to determine if the car is at home. This means the Home Geofence set in Base Configuration is not used for this car. -
+ case HomeDetectionVia.GpsLocation: + +
GPS Location used for home detection
+ TSC will manage charging if the car's GPS Location is within the configured Home Geofence set in Base Configuration. +
+ break; + case HomeDetectionVia.LocatedAtHome: + +
Tesla navigation Home used for home detection
+ TSC will manage charging if the car is at the home location set in the Tesla navigation system. Note: Different driver profiles with different home addresses might mess this up. Make sure that the last driver has the correct home address set in the Tesla navigation system. +
+ break; + case HomeDetectionVia.LocatedAtWork: + +
Tesla navigation Work used for home detection
+ TSC will manage charging if the car is at the work location set in the Tesla navigation system. Note: Different driver profiles with different work addresses might mess this up. Make sure that the last driver has the correct work address set in the Tesla navigation system. +
+ break; + case HomeDetectionVia.LocatedAtFavorite: + +
Tesla navigation Favorite used for home detection
+ TSC will manage charging if the car is at any favorite location set in the Tesla navigation system. Note: TSC is unable to detect at which favorite location the car is, so if you charge at a public charger that is configured as favorite in the Tesla navigation system, TSC tries to manage charging based on your solar production at home and might stop charging if you don't have any solar production at home. Moreover, different driver profiles with different favorite addresses might mess this up. It is HIGHLY recommended to only have one favorite location if this setting is enabled. +
+ break; + default: + throw new ArgumentOutOfRangeException(); } +
+ + @foreach (HomeDetectionVia item in Enum.GetValues(typeof(HomeDetectionVia))) + { + @(item.ToFriendlyName()) + } + +
+ @if (carBasicConfiguration.Item.UseFleetTelemetry) { + For="() => carBasicConfiguration.Item.IncludeTrackingRelevantFields" + OnValueChanged="Redraw" /> } } @@ -253,4 +301,10 @@ else } } + private void UpdateHomeDetectionVia(CarBasicConfiguration item, HomeDetectionVia newItem) + { + item.HomeDetectionVia = newItem; + Redraw(); + } + } \ No newline at end of file diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index 55fc71776..6cdeb75d9 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -189,6 +189,7 @@ public async Task> GetCarBasicConfigurations() BleApiBaseUrl = c.BleApiBaseUrl, UseFleetTelemetry = c.UseFleetTelemetry, IncludeTrackingRelevantFields = c.IncludeTrackingRelevantFields, + HomeDetectionVia = c.HomeDetectionVia, }) .ToListAsync().ConfigureAwait(false); @@ -253,6 +254,7 @@ public async Task UpdateCarBasicConfiguration(int carId, CarBasicConfiguration c databaseCar.BleApiBaseUrl = carBasicConfiguration.BleApiBaseUrl; databaseCar.UseFleetTelemetry = carBasicConfiguration.UseFleetTelemetry; databaseCar.IncludeTrackingRelevantFields = carBasicConfiguration.IncludeTrackingRelevantFields; + databaseCar.HomeDetectionVia = carBasicConfiguration.HomeDetectionVia; await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); var settingsCar = settings.Cars.First(c => c.Id == carId); settingsCar.Name = carBasicConfiguration.Name; diff --git a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs index afe532666..e968bf453 100644 --- a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs +++ b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs @@ -1,6 +1,7 @@ using FluentValidation; using System.ComponentModel; using TeslaSolarCharger.Shared.Attributes; +using TeslaSolarCharger.Shared.Enums; namespace TeslaSolarCharger.Shared.Dtos; @@ -44,6 +45,7 @@ public CarBasicConfiguration(int id, string? name) [HelperText("When enabled, TSC collects data of additional fields that are not necessarily required for TSC to work, but logged data might be helpful for future visualizations. Note: For this a car license is required.")] public bool IncludeTrackingRelevantFields { get; set; } + public HomeDetectionVia HomeDetectionVia { get; set; } } diff --git a/TeslaSolarCharger/Shared/Enums/HomeDetectionVia.cs b/TeslaSolarCharger/Shared/Enums/HomeDetectionVia.cs new file mode 100644 index 000000000..b4e9581d7 --- /dev/null +++ b/TeslaSolarCharger/Shared/Enums/HomeDetectionVia.cs @@ -0,0 +1,9 @@ +namespace TeslaSolarCharger.Shared.Enums; + +public enum HomeDetectionVia +{ + GpsLocation, + LocatedAtHome, + LocatedAtWork, + LocatedAtFavorite, +} diff --git a/TeslaSolarCharger/Shared/Extensions.cs b/TeslaSolarCharger/Shared/Extensions.cs index 25a4bfe15..9e02e00eb 100644 --- a/TeslaSolarCharger/Shared/Extensions.cs +++ b/TeslaSolarCharger/Shared/Extensions.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Text.RegularExpressions; using TeslaSolarCharger.Shared.Enums; using TeslaSolarCharger.SharedModel.Enums; @@ -15,6 +16,18 @@ public static T Next(this T src) where T : struct return (arr.Length==j) ? arr[0] : arr[j]; } + public static string ToFriendlyName(this HomeDetectionVia enumValue) + { + return enumValue switch + { + HomeDetectionVia.GpsLocation => "GPS Location", + HomeDetectionVia.LocatedAtHome => "At Home", + HomeDetectionVia.LocatedAtWork => "At Work", + HomeDetectionVia.LocatedAtFavorite => "At Favorite", + _ => Regex.Replace(enumValue.ToString(), "(\\B[A-Z])", " $1"), + }; + } + public static string ToFriendlyString(this ChargeMode chargeMode) { switch (chargeMode) From 8110f15a3e8f0da772a9c9f403dddb631686566f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 24 Feb 2025 15:53:50 +0100 Subject: [PATCH 2/4] feat(ConfigJsonService): set correct Home detection via --- .../Server/Contracts/IConfigJsonService.cs | 1 + TeslaSolarCharger/Server/Program.cs | 1 + .../Server/Services/ConfigJsonService.cs | 35 +++++++++++++++---- .../Shared/Resources/Constants.cs | 1 + .../Shared/Resources/Contracts/IConstants.cs | 1 + 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs b/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs index 693f4e18f..254aedabf 100644 --- a/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs @@ -16,4 +16,5 @@ public interface IConfigJsonService ISettings GetSettings(); Task AddCarsToSettings(); Task AddBleBaseUrlToAllCars(); + Task SetCorrectHomeDetectionVia(); } diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 98b2600ac..ebaa0bd12 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -200,6 +200,7 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger logger var configJsonService = webApplication.Services.GetRequiredService(); await configJsonService.ConvertOldCarsToNewCar().ConfigureAwait(false); + await configJsonService.SetCorrectHomeDetectionVia().ConfigureAwait(false); await configJsonService.AddBleBaseUrlToAllCars().ConfigureAwait(false); //This needs to be done after converting old cars to new cars as IDs might change await chargingCostService.ConvertToNewChargingProcessStructure().ConfigureAwait(false); diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index 6cdeb75d9..ba8d073e4 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -1,4 +1,3 @@ -using AutoMapper.QueryableExtensions; using Microsoft.EntityFrameworkCore; using System.Runtime.CompilerServices; using Newtonsoft.Json; @@ -10,12 +9,8 @@ using TeslaSolarCharger.Shared.Dtos.Contracts; using TeslaSolarCharger.Shared.Dtos.Settings; using TeslaSolarCharger.Shared.Resources.Contracts; -using TeslaSolarCharger.SharedBackend.Contracts; using TeslaSolarCharger.Shared.Dtos; using TeslaSolarCharger.Shared.Dtos.IndexRazor.CarValues; -using TeslaSolarCharger.Model.EntityFramework; -using System; -using TeslaSolarCharger.Server.Scheduling; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Enums; @@ -29,7 +24,8 @@ public class ConfigJsonService( ITeslaSolarChargerContext teslaSolarChargerContext, IConstants constants, ITeslaMateDbContextWrapper teslaMateDbContextWrapper, - IFleetTelemetryConfigurationService fleetTelemetryConfigurationService) + IFleetTelemetryConfigurationService fleetTelemetryConfigurationService, + ITscConfigurationService tscConfigurationService) : IConfigJsonService { private bool CarConfigurationFileExists() @@ -239,6 +235,33 @@ public async Task AddBleBaseUrlToAllCars() await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } + public async Task SetCorrectHomeDetectionVia() + { + logger.LogTrace("{method}()", nameof(SetCorrectHomeDetectionVia)); + var homeDetectionViaConvertedValue = await tscConfigurationService + .GetConfigurationValueByKey(constants.HomeDetectionViaConvertedKey).ConfigureAwait(false); + if (!string.IsNullOrEmpty(homeDetectionViaConvertedValue)) + { + logger.LogDebug("Home detection via already converted"); + return; + } + + var cars = await teslaSolarChargerContext.Cars.ToListAsync().ConfigureAwait(false); + foreach (var car in cars) + { + if (car.IncludeTrackingRelevantFields) + { + car.HomeDetectionVia = HomeDetectionVia.GpsLocation; + } + else + { + car.HomeDetectionVia = HomeDetectionVia.LocatedAtHome; + } + } + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + await tscConfigurationService.SetConfigurationValueByKey(constants.HomeDetectionViaConvertedKey, "true").ConfigureAwait(false); + } + public async Task UpdateCarBasicConfiguration(int carId, CarBasicConfiguration carBasicConfiguration) { logger.LogTrace("{method}({carId}, {@carBasicConfiguration})", nameof(UpdateCarBasicConfiguration), carId, carBasicConfiguration); diff --git a/TeslaSolarCharger/Shared/Resources/Constants.cs b/TeslaSolarCharger/Shared/Resources/Constants.cs index 8b60d7373..ecf1f7043 100644 --- a/TeslaSolarCharger/Shared/Resources/Constants.cs +++ b/TeslaSolarCharger/Shared/Resources/Constants.cs @@ -44,6 +44,7 @@ public class Constants : IConstants public string BackendTokenStateKey => "BackendTokenState"; public string IsBaseAppLicensedKey => "IsBaseAppLicensed"; public string IsFleetApiLicensedKey => "IsFleetApiLicensed_"; + public string HomeDetectionViaConvertedKey => "HomeDetectionViaConverted"; public string FleetTelemetryConfigurationExpiryKey => "FleetTelemetryConfigurationExpiry_"; //Also on Cloud Server in Solar4Car.Backend.Helper.Constants public int FleetTelemetryReconfigurationBufferHours => 3; diff --git a/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs b/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs index 4d941fff3..494ed697d 100644 --- a/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs +++ b/TeslaSolarCharger/Shared/Resources/Contracts/IConstants.cs @@ -46,4 +46,5 @@ public interface IConstants string IsFleetApiLicensedKey { get; } string FleetTelemetryConfigurationExpiryKey { get; } int FleetTelemetryReconfigurationBufferHours { get; } + string HomeDetectionViaConvertedKey { get; } } From ca21380be8d3de66befb7a75d7dd8cb41982923c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 24 Feb 2025 16:19:10 +0100 Subject: [PATCH 3/4] feat(chore): use selected home detection via --- .../Server/Services/ChargingService.cs | 8 ++---- .../Server/Services/ConfigJsonService.cs | 8 ++++-- .../FleetTelemetryWebSocketService.cs | 28 +++++++++++++++---- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/TeslaSolarCharger/Server/Services/ChargingService.cs b/TeslaSolarCharger/Server/Services/ChargingService.cs index 43d60c98c..cc3647b47 100644 --- a/TeslaSolarCharger/Server/Services/ChargingService.cs +++ b/TeslaSolarCharger/Server/Services/ChargingService.cs @@ -160,14 +160,12 @@ private async Task CalculateGeofences() continue; } - var fleetTelemetrySettings = await context.Cars + var homeDetectionVia = await context.Cars .Where(c => c.Id == car.Id) - .Select(c => new { c.UseFleetTelemetry, c.IncludeTrackingRelevantFields }) + .Select(c => c.HomeDetectionVia) .FirstAsync(); - if (fleetTelemetrySettings.UseFleetTelemetry - && !fleetTelemetrySettings.IncludeTrackingRelevantFields - && configurationWrapper.GetVehicleDataFromTesla()) + if (homeDetectionVia != HomeDetectionVia.GpsLocation) { logger.LogDebug("Car {carId} uses fleet telemetry but does not include tracking relevant fields. Do not calculate geofence", car.Id); car.DistanceToHomeGeofence = null; diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index ba8d073e4..49d02475b 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -249,13 +249,15 @@ public async Task SetCorrectHomeDetectionVia() var cars = await teslaSolarChargerContext.Cars.ToListAsync().ConfigureAwait(false); foreach (var car in cars) { - if (car.IncludeTrackingRelevantFields) + if (car.UseFleetTelemetry + && !car.IncludeTrackingRelevantFields + && configurationWrapper.GetVehicleDataFromTesla()) { - car.HomeDetectionVia = HomeDetectionVia.GpsLocation; + car.HomeDetectionVia = HomeDetectionVia.LocatedAtHome; } else { - car.HomeDetectionVia = HomeDetectionVia.LocatedAtHome; + car.HomeDetectionVia = HomeDetectionVia.GpsLocation; } } await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index aeca1b05d..68f4134b0 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -247,6 +247,16 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str var settings = scope.ServiceProvider.GetRequiredService(); var settingsCar = settings.Cars.First(c => c.Vin == vin); string? propertyName = null; + HomeDetectionVia? homeDetectionVia = null; + if (message.Type == CarValueType.LocatedAtHome + || message.Type == CarValueType.LocatedAtWork + || message.Type == CarValueType.LocatedAtFavorite) + { + homeDetectionVia = await context.Cars + .Where(c => c.Id == settingsCar.Id) + .Select(c => c.HomeDetectionVia) + .FirstAsync(); + } switch (message.Type) { case CarValueType.ChargeAmps: @@ -318,11 +328,19 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str } break; case CarValueType.LocatedAtHome: - var fleetTelemetrySettings = await context.Cars - .Where(c => c.Id == settingsCar.Id) - .Select(c => new { c.UseFleetTelemetry, c.IncludeTrackingRelevantFields }) - .FirstAsync(); - if (fleetTelemetrySettings.UseFleetTelemetry && !fleetTelemetrySettings.IncludeTrackingRelevantFields) + if (homeDetectionVia == HomeDetectionVia.LocatedAtHome) + { + propertyName = nameof(DtoCar.IsHomeGeofence); + } + break; + case CarValueType.LocatedAtWork: + if (homeDetectionVia == HomeDetectionVia.LocatedAtWork) + { + propertyName = nameof(DtoCar.IsHomeGeofence); + } + break; + case CarValueType.LocatedAtFavorite: + if (homeDetectionVia == HomeDetectionVia.LocatedAtFavorite) { propertyName = nameof(DtoCar.IsHomeGeofence); } From 53a050dab25937e82330c96d8a5c5814b331537e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 24 Feb 2025 17:07:22 +0100 Subject: [PATCH 4/4] feat(CarBasicConfigurationValidator): validate HomeDetectionVia --- .../Client/Pages/CarSettings.razor | 5 +++-- .../Client/Wrapper/EditableItem.cs | 20 ++++++++++++------- .../CarBasicConfigurationValidator.cs | 13 +++++++++++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/CarSettings.razor b/TeslaSolarCharger/Client/Pages/CarSettings.razor index d57e5b96c..5f2b451a2 100644 --- a/TeslaSolarCharger/Client/Pages/CarSettings.razor +++ b/TeslaSolarCharger/Client/Pages/CarSettings.razor @@ -109,6 +109,7 @@ else }
+ OnValueChanged="newValue => { UpdateGetlocationDataViaFleetTelemetry(carBasicConfiguration.Item, newValue); carBasicConfiguration.ClearErrors(nameof(carBasicConfiguration.Item.IncludeTrackingRelevantFields), nameof(carBasicConfiguration.Item.HomeDetectionVia));}" /> @if (carBasicConfiguration.Item.UseFleetTelemetry) { + OnValueChanged="_ => { carBasicConfiguration.ClearErrors(nameof(carBasicConfiguration.Item.HomeDetectionVia)); Redraw(); }" /> } } diff --git a/TeslaSolarCharger/Client/Wrapper/EditableItem.cs b/TeslaSolarCharger/Client/Wrapper/EditableItem.cs index ad1b921f9..0efabea69 100644 --- a/TeslaSolarCharger/Client/Wrapper/EditableItem.cs +++ b/TeslaSolarCharger/Client/Wrapper/EditableItem.cs @@ -29,17 +29,23 @@ public EditableItem(T item) /// /// Removes all validation errors for the specified property name. /// - /// The name of the property for which to clear errors. - public void ClearErrors(string propertyName) + /// The names of the properties for which to clear errors. + public void ClearErrors(params string[] propertyNames) { - if (string.IsNullOrEmpty(propertyName)) return; + if (propertyNames.Length == 0) + { + return; + } - var fieldIdentifier = new FieldIdentifier(Item, propertyName); + foreach (var propertyName in propertyNames) + { + if (string.IsNullOrEmpty(propertyName)) continue; - // Clear validation messages for the specified property - MessageStore.Clear(fieldIdentifier); + var fieldIdentifier = new FieldIdentifier(Item, propertyName); + MessageStore.Clear(fieldIdentifier); + } - // Notify the EditContext to update the UI + // Notify the EditContext to update the UI once after all properties are cleared EditContext.NotifyValidationStateChanged(); } } diff --git a/TeslaSolarCharger/Server/ServerValidators/CarBasicConfigurationValidator.cs b/TeslaSolarCharger/Server/ServerValidators/CarBasicConfigurationValidator.cs index f1834fe58..1f0ca114a 100644 --- a/TeslaSolarCharger/Server/ServerValidators/CarBasicConfigurationValidator.cs +++ b/TeslaSolarCharger/Server/ServerValidators/CarBasicConfigurationValidator.cs @@ -1,4 +1,4 @@ -using FluentValidation; +using FluentValidation; using Microsoft.EntityFrameworkCore; using TeslaSolarCharger.Model.Contracts; using TeslaSolarCharger.Server.Services; @@ -44,6 +44,9 @@ public CarBasicConfigurationValidator(IConfigurationWrapper configurationWrapper RuleFor(x => x.IncludeTrackingRelevantFields) .Equal(false) .WithMessage("Tracking relevant fields can only be included if Fleet Telemetry is enabled."); + RuleFor(x => x.HomeDetectionVia) + .Equal(HomeDetectionVia.GpsLocation) + .WithMessage("Without Fleet Telemetry only home detection via GPS location is supported."); }); When(x => x.UseFleetTelemetry, () => @@ -55,6 +58,14 @@ public CarBasicConfigurationValidator(IConfigurationWrapper configurationWrapper return !includeTrackingRelevantFields || hasFleetApiLicense; }) .WithMessage("Car not licensed for Fleet API. Manage Fleet API subscriptions via https://solar4car.com/subscriptions."); + + When(x => x.IncludeTrackingRelevantFields == false && isTeslaMateDataSource == false, () => + { + RuleFor(x => x.HomeDetectionVia) + .NotEqual(HomeDetectionVia.GpsLocation) + .WithMessage("GPS location can not be used for home detection if tracking relevant fields are not enabled."); + }); + });