From 2131dcafa7e3586de7c76919b2eae181732203b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Thu, 5 Dec 2024 11:03:33 +0100 Subject: [PATCH 1/8] feat(FleetTelemetryWebSocketService): Improve logging why fleet telemetry disconnects --- .../Server/Services/FleetTelemetryWebSocketService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index 8a28987eb..80ace55ec 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -79,7 +79,7 @@ await existingClient.WebSocketClient.SendAsync(segment, WebSocketMessageType.Tex continue; } - logger.LogInformation("Websocket Client for car {vin} is not open or last heartbeat is too old. Disposing client", car.Vin); + logger.LogInformation("Websocket Client State for car {vin} is {state}, last heartbeat is {lastHeartbeat} while earliest Possible Heartbeat is {earliestPossibleHeartbeat}. Disposing client", car.Vin, existingClient.WebSocketClient.State, existingClient.LastReceivedHeartbeat, earliestPossibleLastHeartbeat); existingClient.WebSocketClient.Dispose(); Clients.Remove(existingClient); } From 2bfab55b4dffffd094d3e94ba7ccc25aef65e854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Thu, 5 Dec 2024 11:50:56 +0100 Subject: [PATCH 2/8] feat(BleController): can get car states via ble --- .../Server/Controllers/BleController.cs | 6 ++++ .../Server/Services/Contracts/IBleService.cs | 2 ++ .../FleetTelemetryWebSocketService.cs | 3 +- .../Server/Services/TeslaBleService.cs | 31 ++++++++++++++++++- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/TeslaSolarCharger/Server/Controllers/BleController.cs b/TeslaSolarCharger/Server/Controllers/BleController.cs index 5c1cb0e3a..2d0ae8d0f 100644 --- a/TeslaSolarCharger/Server/Controllers/BleController.cs +++ b/TeslaSolarCharger/Server/Controllers/BleController.cs @@ -24,4 +24,10 @@ public class BleController (IBleService bleService) : ApiBaseController [HttpGet] public Task WakeUp(string vin) => bleService.WakeUpCar(vin); + + [HttpGet] + public Task GetChargeState(string vin) => bleService.GetChargeState(vin); + + [HttpGet] + public Task GetDriveState(string vin) => bleService.GetDriveState(vin); } diff --git a/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs b/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs index 98312a280..0fca075b5 100644 --- a/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs +++ b/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs @@ -13,4 +13,6 @@ public interface IBleService Task PairKey(string vin, string role); Task WakeUpCar(string vin); Task CheckBleApiVersionCompatibilities(); + Task GetChargeState(string vin); + Task GetDriveState(string vin); } diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index 80ace55ec..d5c607530 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -79,7 +79,8 @@ await existingClient.WebSocketClient.SendAsync(segment, WebSocketMessageType.Tex continue; } - logger.LogInformation("Websocket Client State for car {vin} is {state}, last heartbeat is {lastHeartbeat} while earliest Possible Heartbeat is {earliestPossibleHeartbeat}. Disposing client", car.Vin, existingClient.WebSocketClient.State, existingClient.LastReceivedHeartbeat, earliestPossibleLastHeartbeat); + logger.LogInformation("Websocket Client State for car {vin} is {state}, last heartbeat is {lastHeartbeat} while earliest Possible Heartbeat is {earliestPossibleHeartbeat}. Disposing client", + car.Vin, existingClient.WebSocketClient.State, existingClient.LastReceivedHeartbeat, earliestPossibleLastHeartbeat); existingClient.WebSocketClient.Dispose(); Clients.Remove(existingClient); } diff --git a/TeslaSolarCharger/Server/Services/TeslaBleService.cs b/TeslaSolarCharger/Server/Services/TeslaBleService.cs index 31ccfc5f8..8aaee08a1 100644 --- a/TeslaSolarCharger/Server/Services/TeslaBleService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaBleService.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using LanguageExt; +using Newtonsoft.Json; using System.Net; using System.Web; using TeslaSolarCharger.Server.Dtos.Ble; @@ -41,6 +42,34 @@ public async Task WakeUpCar(string vin) return result; } + public async Task GetChargeState(string vin) + { + //Not tested, should contain a json with the charge state. Other options would be climate, drive, closures, charge-schedule, precondition-schedule, tire-pressue, media, media-detail, software-update + logger.LogTrace("{method}({vin})", nameof(GetChargeState), vin); + var request = new DtoBleRequest + { + Vin = vin, + CommandName = "state", + Parameters = ["charge"], + }; + var result = await SendCommandToBle(request).ConfigureAwait(false); + return result; + } + + public async Task GetDriveState(string vin) + { + //Not tested + logger.LogTrace("{method}({vin})", nameof(GetDriveState), vin); + var request = new DtoBleRequest + { + Vin = vin, + CommandName = "state", + Parameters = ["drive"], + }; + var result = await SendCommandToBle(request).ConfigureAwait(false); + return result; + } + public async Task StopCharging(string vin) { var request = new DtoBleRequest From 76127ebb500d7544c5caca8de4a9520bac9c2378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 13:49:23 +0100 Subject: [PATCH 3/8] feat(CarConfig): always use ble for wakeup if ble is enabled --- .../Entities/TeslaSolarCharger/Car.cs | 1 - TeslaSolarCharger/Client/Pages/CarSettings.razor | 5 +---- .../Server/Scheduling/JobFactory.cs | 16 ++++------------ .../Server/Services/ConfigJsonService.cs | 2 -- .../Services/FleetTelemetryWebSocketService.cs | 2 +- .../Server/Services/TeslaFleetApiService.cs | 3 ++- .../Shared/Dtos/CarBasicConfiguration.cs | 3 --- TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs | 1 - 8 files changed, 8 insertions(+), 25 deletions(-) diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs index 51ba08f94..ece3589f0 100644 --- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs +++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs @@ -46,7 +46,6 @@ public class Car public DateTime? WakeUpRateLimitedUntil { get; set; } public DateTime? ChargingCommandsRateLimitedUntil { get; set; } public bool UseBle { get; set; } - public bool UseBleForWakeUp { get; set; } public string? BleApiBaseUrl { get; set; } public bool UseFleetTelemetry { get; set; } public bool UseFleetTelemetryForLocationData { get; set; } diff --git a/TeslaSolarCharger/Client/Pages/CarSettings.razor b/TeslaSolarCharger/Client/Pages/CarSettings.razor index 56dea37aa..9228bcfb6 100644 --- a/TeslaSolarCharger/Client/Pages/CarSettings.razor +++ b/TeslaSolarCharger/Client/Pages/CarSettings.razor @@ -59,9 +59,6 @@ else - } - @if (carBasicConfiguration.UseBleForWakeUp) + @if (carBasicConfiguration.UseBle) {
Test Wakeup via BLE
diff --git a/TeslaSolarCharger/Server/Scheduling/JobFactory.cs b/TeslaSolarCharger/Server/Scheduling/JobFactory.cs index a24829a72..e226c0305 100644 --- a/TeslaSolarCharger/Server/Scheduling/JobFactory.cs +++ b/TeslaSolarCharger/Server/Scheduling/JobFactory.cs @@ -4,23 +4,15 @@ namespace TeslaSolarCharger.Server.Scheduling; -public class JobFactory : IJobFactory +public class JobFactory(ILogger logger, IServiceProvider serviceProvider) : IJobFactory { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; private readonly ConcurrentDictionary _scopes = new(); - public JobFactory(ILogger logger, IServiceProvider serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } - public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { - _logger.LogTrace("{Method} ({@bundle}, {scheduler})", nameof(NewJob), bundle, scheduler); + logger.LogTrace("{Method} ({@bundle}, {scheduler})", nameof(NewJob), bundle, scheduler); var jobType = bundle.JobDetail.JobType; - var scope = _serviceProvider.CreateScope(); + var scope = serviceProvider.CreateScope(); var job = (IJob)scope.ServiceProvider.GetRequiredService(jobType); _scopes.TryAdd(job, scope); return job; @@ -28,7 +20,7 @@ public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) public void ReturnJob(IJob job) { - _logger.LogTrace("{class}.{method}({job})", nameof(JobFactory), nameof(ReturnJob), job.GetType().FullName); + logger.LogTrace("{class}.{method}({job})", nameof(JobFactory), nameof(ReturnJob), job.GetType().FullName); if (_scopes.TryGetValue(job, out var scope)) { scope.Dispose(); diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index 8934fa8a9..1fc4264bf 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -245,7 +245,6 @@ public async Task UpdateCarBasicConfiguration(int carId, CarBasicConfiguration c databaseCar.ChargingPriority = carBasicConfiguration.ChargingPriority; databaseCar.ShouldBeManaged = carBasicConfiguration.ShouldBeManaged; databaseCar.UseBle = carBasicConfiguration.UseBle; - databaseCar.UseBleForWakeUp = carBasicConfiguration.UseBleForWakeUp; databaseCar.BleApiBaseUrl = carBasicConfiguration.BleApiBaseUrl; databaseCar.UseFleetTelemetry = carBasicConfiguration.UseFleetTelemetry; databaseCar.UseFleetTelemetryForLocationData = carBasicConfiguration.UseFleetTelemetryForLocationData; @@ -259,7 +258,6 @@ public async Task UpdateCarBasicConfiguration(int carId, CarBasicConfiguration c settingsCar.ChargingPriority = carBasicConfiguration.ChargingPriority; settingsCar.ShouldBeManaged = carBasicConfiguration.ShouldBeManaged; settingsCar.UseBle = carBasicConfiguration.UseBle; - settingsCar.UseBleForWakeUp = carBasicConfiguration.UseBleForWakeUp; settingsCar.BleApiBaseUrl = carBasicConfiguration.BleApiBaseUrl; await fleetTelemetryWebSocketService.DisconnectWebSocketsByVin(carBasicConfiguration.Vin); } diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index d5c607530..fc33ba1d9 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -194,7 +194,7 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str client.LastReceivedHeartbeat = dateTimeProvider.UtcNow(); continue; } - + logger.LogTrace("Received non heartbeate message."); var message = DeserializeFleetTelemetryMessage(jsonMessage); if (message == default) { diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 2bd67a421..500f37bf1 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -96,6 +96,7 @@ public class TeslaFleetApiService( RequestUrl = constants.WakeUpRequestUrl, NeedsProxy = false, TeslaApiRequestType = TeslaApiRequestType.WakeUp, + BleCompatible = true, }; private DtoFleetApiRequest VehicleRequest => new() @@ -821,7 +822,7 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState) { logger.LogTrace("{method}({vin}, {@fleetApiRequest}, {contentData})", nameof(SendCommandToTeslaApi), vin, fleetApiRequest, contentData); var car = settings.Cars.First(c => c.Vin == vin); - if (fleetApiRequest.BleCompatible || (fleetApiRequest.RequestUrl == WakeUpRequest.RequestUrl && car.UseBleForWakeUp)) + if (fleetApiRequest.BleCompatible) { var isCarBleEnabled = car.UseBle; diff --git a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs index 6bba68542..e1ae90296 100644 --- a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs +++ b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs @@ -41,9 +41,6 @@ public CarBasicConfiguration(int id, string? name) [DisplayName("Use BLE")] [HelperText("Use BLE communication to go around Tesla rate limits. Note: A BLE device (e.g., Raspberry Pi) with installed TeslaSolarChargerBle Container needs to be near (max 4 meters without any walls in between) your car.")] public bool UseBle { get; set; } - [DisplayName("Use BLE for wake up")] - [HelperText("The car needs to be at least on version 2024.32.3 to support wake up via BLE.")] - public bool UseBleForWakeUp { 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("Only supported on cars with Software 2024.38.2+. Not supported on Pre 2021 Model S/X. 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.")] diff --git a/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs b/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs index 55866997d..ecbd81d8a 100644 --- a/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs +++ b/TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs @@ -87,7 +87,6 @@ private int? ChargingPower public bool? Healthy { get; set; } public bool ReducedChargeSpeedWarning { get; set; } public bool UseBle { get; set; } - public bool UseBleForWakeUp { get; set; } public string? BleApiBaseUrl { get; set; } public DateTime? EarliestHomeArrival { get; set; } public List PlannedChargingSlots { get; set; } = new List(); From 6ce243dc39b039743484b56690dfbd38ffbc3629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 13:51:09 +0100 Subject: [PATCH 4/8] feat(EF): remove usebleForwakeup field --- ...41207125046_RemoveUseBleWakeup.Designer.cs | 923 ++++++++++++++++++ .../20241207125046_RemoveUseBleWakeup.cs | 29 + .../TeslaSolarChargerContextModelSnapshot.cs | 3 - 3 files changed, 952 insertions(+), 3 deletions(-) create mode 100644 TeslaSolarCharger.Model/Migrations/20241207125046_RemoveUseBleWakeup.Designer.cs create mode 100644 TeslaSolarCharger.Model/Migrations/20241207125046_RemoveUseBleWakeup.cs diff --git a/TeslaSolarCharger.Model/Migrations/20241207125046_RemoveUseBleWakeup.Designer.cs b/TeslaSolarCharger.Model/Migrations/20241207125046_RemoveUseBleWakeup.Designer.cs new file mode 100644 index 000000000..20d88ade0 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20241207125046_RemoveUseBleWakeup.Designer.cs @@ -0,0 +1,923 @@ +// +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("20241207125046_RemoveUseBleWakeup")] + partial class RemoveUseBleWakeup + { + /// + 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("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("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/20241207125046_RemoveUseBleWakeup.cs b/TeslaSolarCharger.Model/Migrations/20241207125046_RemoveUseBleWakeup.cs new file mode 100644 index 000000000..fbd7a1bc8 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20241207125046_RemoveUseBleWakeup.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + /// + public partial class RemoveUseBleWakeup : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UseBleForWakeUp", + table: "Cars"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UseBleForWakeUp", + table: "Cars", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs index 8d5f6aded..906678952 100644 --- a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs +++ b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs @@ -186,9 +186,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UseBle") .HasColumnType("INTEGER"); - b.Property("UseBleForWakeUp") - .HasColumnType("INTEGER"); - b.Property("UseFleetTelemetry") .HasColumnType("INTEGER"); From b4c6dc1cca66a15d08939282cf1839e06295e35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 14:52:35 +0100 Subject: [PATCH 5/8] feat(CarConfigurationService): hide cars missing in Tesla account --- .../Entities/TeslaSolarCharger/Car.cs | 1 + ...9_AddIsAvailableInTeslaAccount.Designer.cs | 926 ++++++++++++++++++ ...1207134909_AddIsAvailableInTeslaAccount.cs | 29 + .../TeslaSolarChargerContextModelSnapshot.cs | 3 + .../Services/CarConfigurationService.cs | 17 + .../Server/Services/ConfigJsonService.cs | 4 +- 6 files changed, 979 insertions(+), 1 deletion(-) create mode 100644 TeslaSolarCharger.Model/Migrations/20241207134909_AddIsAvailableInTeslaAccount.Designer.cs create mode 100644 TeslaSolarCharger.Model/Migrations/20241207134909_AddIsAvailableInTeslaAccount.cs diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs index ece3589f0..bd5ec6f18 100644 --- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs +++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs @@ -49,6 +49,7 @@ public class Car public string? BleApiBaseUrl { get; set; } public bool UseFleetTelemetry { get; set; } public bool UseFleetTelemetryForLocationData { get; set; } + public bool IsAvailableInTeslaAccount { get; set; } public string? WakeUpCalls { get; set; } public string? VehicleDataCalls { get; set; } diff --git a/TeslaSolarCharger.Model/Migrations/20241207134909_AddIsAvailableInTeslaAccount.Designer.cs b/TeslaSolarCharger.Model/Migrations/20241207134909_AddIsAvailableInTeslaAccount.Designer.cs new file mode 100644 index 000000000..7f11d1ced --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20241207134909_AddIsAvailableInTeslaAccount.Designer.cs @@ -0,0 +1,926 @@ +// +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("20241207134909_AddIsAvailableInTeslaAccount")] + partial class AddIsAvailableInTeslaAccount + { + /// + 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("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("IsAvailableInTeslaAccount") + .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("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/20241207134909_AddIsAvailableInTeslaAccount.cs b/TeslaSolarCharger.Model/Migrations/20241207134909_AddIsAvailableInTeslaAccount.cs new file mode 100644 index 000000000..dee7fc494 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20241207134909_AddIsAvailableInTeslaAccount.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + /// + public partial class AddIsAvailableInTeslaAccount : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsAvailableInTeslaAccount", + table: "Cars", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsAvailableInTeslaAccount", + table: "Cars"); + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs index 906678952..e4f729503 100644 --- a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs +++ b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs @@ -132,6 +132,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IgnoreLatestTimeToReachSocDateOnWeekend") .HasColumnType("INTEGER"); + b.Property("IsAvailableInTeslaAccount") + .HasColumnType("INTEGER"); + b.Property("LatestTimeToReachSoC") .HasColumnType("TEXT"); diff --git a/TeslaSolarCharger/Server/Services/CarConfigurationService.cs b/TeslaSolarCharger/Server/Services/CarConfigurationService.cs index f67557fe9..07f4a4ddb 100644 --- a/TeslaSolarCharger/Server/Services/CarConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/CarConfigurationService.cs @@ -56,6 +56,12 @@ public async Task AddAllMissingCarsFromTeslaAccount() teslaSolarChargerCar.TeslaMateCarId = teslaMateCarId; await teslaSolarChargerContext.SaveChangesAsync(); } + + if (!teslaSolarChargerCar.IsAvailableInTeslaAccount) + { + teslaSolarChargerCar.IsAvailableInTeslaAccount = true; + await teslaSolarChargerContext.SaveChangesAsync(); + } continue; } teslaSolarChargerCar = new Car @@ -78,5 +84,16 @@ public async Task AddAllMissingCarsFromTeslaAccount() teslaSolarChargerContext.Cars.Add(teslaSolarChargerCar); await teslaSolarChargerContext.SaveChangesAsync(); } + + foreach (var teslaSolarChargerCar in teslaSolarChargerCars) + { + if (!teslaAccountCars.Any(c => string.Equals(c.Vin, teslaSolarChargerCar.Vin))) + { + logger.LogInformation("Car with VIN {vin} is not available in Tesla account anymore.", teslaSolarChargerCar.Vin); + teslaSolarChargerCar.IsAvailableInTeslaAccount = false; + teslaSolarChargerCar.ShouldBeManaged = false; + await teslaSolarChargerContext.SaveChangesAsync(); + } + } } } diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index 1fc4264bf..aa6265fd9 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -184,6 +184,8 @@ public async Task> GetCarBasicConfigurations() }); var cars = await teslaSolarChargerContext.Cars + .Where(c => c.IsAvailableInTeslaAccount) + .OrderBy(c => c.ChargingPriority) .ProjectTo(mapper) .ToListAsync().ConfigureAwait(false); @@ -433,7 +435,7 @@ public async Task UpdateAverageGridVoltage() else { var chargerVoltages = await teslaSolarChargerContext.ChargingDetails - .Where(c => c.ChargerVoltage != null) + .Where(c => c.ChargerVoltage != null && c.ChargerVoltage > 40) .OrderByDescending(c => c.Id) .Select(c => c.ChargerVoltage) .Take(1000) From 5efbdbe89bd463301596340402958429c99d8c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 15:03:23 +0100 Subject: [PATCH 6/8] feat(ConfigJsonService): use charging details for grid voltage calculation --- .../Server/Services/ConfigJsonService.cs | 46 +++++-------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index aa6265fd9..e7fc1b927 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -407,47 +407,25 @@ private async Task AddCachedCarStatesToCars(List cars) public async Task UpdateAverageGridVoltage() { logger.LogTrace("{method}()", nameof(UpdateAverageGridVoltage)); - var homeGeofence = configurationWrapper.GeoFence(); const int lowestWorldWideGridVoltage = 100; const int voltageBuffer = 15; const int lowestGridVoltageToSearchFor = lowestWorldWideGridVoltage - voltageBuffer; try { - var teslaMateContext = teslaMateDbContextWrapper.GetTeslaMateContextIfAvailable(); - if (teslaMateContext != default && configurationWrapper.UseTeslaMateIntegration()) + var chargerVoltages = await teslaSolarChargerContext.ChargingDetails + .Where(c => (c.ChargerVoltage != null) + && (c.ChargerVoltage > lowestGridVoltageToSearchFor)) + .OrderByDescending(c => c.Id) + .Select(c => c.ChargerVoltage) + .Take(1000) + .ToListAsync().ConfigureAwait(false); + if (chargerVoltages.Count > 10) { - var chargerVoltages = await teslaMateContext - .Charges - .Where(c => c.ChargingProcess.Geofence != null - && c.ChargingProcess.Geofence.Name == homeGeofence - && c.ChargerVoltage > lowestGridVoltageToSearchFor) - .OrderByDescending(c => c.Id) - .Select(c => c.ChargerVoltage) - .Take(1000) - .ToListAsync().ConfigureAwait(false); - if (chargerVoltages.Count > 10) - { - var averageValue = Convert.ToInt32(chargerVoltages.Average(c => c!.Value)); - logger.LogDebug("Use {averageVoltage}V for charge speed calculation", averageValue); - settings.AverageHomeGridVoltage = averageValue; - } + var averageValue = Convert.ToInt32(chargerVoltages.Average(c => c!.Value)); + logger.LogDebug("Use {averageVoltage}V for charge speed calculation", averageValue); + settings.AverageHomeGridVoltage = averageValue; } - else - { - var chargerVoltages = await teslaSolarChargerContext.ChargingDetails - .Where(c => c.ChargerVoltage != null && c.ChargerVoltage > 40) - .OrderByDescending(c => c.Id) - .Select(c => c.ChargerVoltage) - .Take(1000) - .ToListAsync().ConfigureAwait(false); - if (chargerVoltages.Count > 10) - { - var averageValue = Convert.ToInt32(chargerVoltages.Average(c => c!.Value)); - logger.LogDebug("Use {averageVoltage}V for charge speed calculation", averageValue); - settings.AverageHomeGridVoltage = averageValue; - } - } - + } catch (Exception ex) { From bbe6f114c518fbe655c60596567cfa5a2df006a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 16:25:37 +0100 Subject: [PATCH 7/8] feat(FleetTelemetryWebSocketService): can handle error messages --- .../DtoFleetTelemetryErrorMessage.cs | 8 +++ .../DtoTscFleetTelemetryMessage.cs | 2 +- .../FleetTelemetryWebSocketService.cs | 63 ++++++++++++++++++- .../Shared/Dtos/CarBasicConfiguration.cs | 2 +- 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs rename TeslaSolarCharger/Server/Dtos/{ => FleetTelemetry}/DtoTscFleetTelemetryMessage.cs (88%) diff --git a/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs new file mode 100644 index 000000000..3e2dc2740 --- /dev/null +++ b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs @@ -0,0 +1,8 @@ +namespace TeslaSolarCharger.Server.Dtos.FleetTelemetry; + +public class DtoFleetTelemetryErrorMessage +{ + public List MissingKeyVins { get; set; } = new List(); + public List UnsupportedHardwareVins { get; set; } = new List(); + public List UnsupportedFirmwareVins { get; set; } = new List(); +} diff --git a/TeslaSolarCharger/Server/Dtos/DtoTscFleetTelemetryMessage.cs b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoTscFleetTelemetryMessage.cs similarity index 88% rename from TeslaSolarCharger/Server/Dtos/DtoTscFleetTelemetryMessage.cs rename to TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoTscFleetTelemetryMessage.cs index 95cc6d67e..7e2ad0fce 100644 --- a/TeslaSolarCharger/Server/Dtos/DtoTscFleetTelemetryMessage.cs +++ b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoTscFleetTelemetryMessage.cs @@ -1,6 +1,6 @@ using TeslaSolarCharger.Shared.Enums; -namespace TeslaSolarCharger.Server.Dtos; +namespace TeslaSolarCharger.Server.Dtos.FleetTelemetry; public class DtoTscFleetTelemetryMessage { diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index fc33ba1d9..c25d361a4 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -6,6 +6,7 @@ using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; using TeslaSolarCharger.Model.EntityFramework; using TeslaSolarCharger.Server.Dtos; +using TeslaSolarCharger.Server.Dtos.FleetTelemetry; using TeslaSolarCharger.Server.Helper; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Contracts; @@ -39,7 +40,9 @@ public async Task ReconnectWebSocketsForEnabledCars() var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var cars = await context.Cars - .Where(c => c.UseFleetTelemetry && (c.ShouldBeManaged == true)) + .Where(c => c.UseFleetTelemetry + && (c.ShouldBeManaged == true) + && (c.TeslaFleetApiState != TeslaCarFleetApiState.NotWorking)) .Select(c => new { c.Vin, c.UseFleetTelemetryForLocationData, }) .ToListAsync(); var bytesToSend = Encoding.UTF8.GetBytes("Heartbeat"); @@ -198,7 +201,11 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str var message = DeserializeFleetTelemetryMessage(jsonMessage); if (message == default) { - logger.LogWarning("Could not deserialize non heartbeat message {string}", jsonMessage); + var couldHandleErrorMessage = await HandleErrorMessage(jsonMessage); + if (!couldHandleErrorMessage) + { + logger.LogWarning("Could not deserialize non heartbeat message {string}", jsonMessage); + } continue; } @@ -294,6 +301,58 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str } } + private async Task HandleErrorMessage(string jsonMessage) + { + logger.LogTrace("{method}({jsonMessage}", nameof(HandleErrorMessage), jsonMessage); + var message = JsonConvert.DeserializeObject(jsonMessage); + if(message == default) + { + return false; + } + foreach (var vin in message.MissingKeyVins) + { + var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var car = context.Cars.FirstOrDefault(c => c.Vin == vin); + if (car == default) + { + continue; + } + logger.LogInformation("Set Fleet API state for car {vin} to not working", vin); + car.TeslaFleetApiState = TeslaCarFleetApiState.NotWorking; + await context.SaveChangesAsync(); + } + + foreach (var vin in message.UnsupportedFirmwareVins) + { + logger.LogInformation("Disable Fleet Telemetry for car {vin} as firmware is not supported", vin); + await DisableFleetTelemetryForCar(vin).ConfigureAwait(false); + } + + foreach (var vin in message.UnsupportedHardwareVins) + { + logger.LogInformation("Disable Fleet Telemetry for car {vin} as hardware is not supported", vin); + await DisableFleetTelemetryForCar(vin).ConfigureAwait(false); + } + + return true; + } + + private async Task DisableFleetTelemetryForCar(string vin) + { + logger.LogTrace("{method}({vin})", nameof(DisableFleetTelemetryForCar), vin); + var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var car = context.Cars.FirstOrDefault(c => c.Vin == vin); + if (car == default) + { + return; + } + car.UseFleetTelemetry = false; + car.UseFleetTelemetryForLocationData = false; + await context.SaveChangesAsync(); + } + internal DtoTscFleetTelemetryMessage? DeserializeFleetTelemetryMessage(string jsonMessage) { var settings = new JsonSerializerSettings diff --git a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs index e1ae90296..2d546f9a8 100644 --- a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs +++ b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs @@ -43,7 +43,7 @@ public CarBasicConfiguration(int id, string? name) public bool UseBle { 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("Only supported on cars with Software 2024.38.2+. Not supported on Pre 2021 Model S/X. 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("Only supported on cars with Software 2024.38.2+. Not supported on Pre 2021 Model S/X. 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. If your car does not support fleet telemetry, this option will be disabled automatically within two minutes.")] public bool UseFleetTelemetry { get; set; } [HelperText("This further improves the detection if the car is at home. Enabling this results in additionally streaming the field Location over my server. If you do not mind that your car location data passes my server, do not disable this option.")] From 60030cf820cf2b672bdb74b7c963add41ad6fcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 17:17:53 +0100 Subject: [PATCH 8/8] feat(FleetTelemetryWebSocketService): can receive error messages --- .../FleetTelemetry/FleetTelemetryMessageBase.cs | 8 ++++++++ .../Server/Enums/FleetTelemetryMessageType.cs | 7 +++++++ .../Services/FleetTelemetryWebSocketService.cs | 16 ++++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs create mode 100644 TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs diff --git a/TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs new file mode 100644 index 000000000..ea4942a7f --- /dev/null +++ b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs @@ -0,0 +1,8 @@ +using TeslaSolarCharger.Server.Enums; + +namespace TeslaSolarCharger.Server.Dtos.FleetTelemetry; + +public class FleetTelemetryMessageBase +{ + public FleetTelemetryMessageType MessageType { get; set; } +} diff --git a/TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs b/TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs new file mode 100644 index 000000000..674ecfcce --- /dev/null +++ b/TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs @@ -0,0 +1,7 @@ +namespace TeslaSolarCharger.Server.Enums; + +public enum FleetTelemetryMessageType +{ + Telemetry, + Error, +} diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index c25d361a4..83a2786c9 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System.Globalization; using System.Net.WebSockets; using System.Text; @@ -7,6 +8,7 @@ using TeslaSolarCharger.Model.EntityFramework; using TeslaSolarCharger.Server.Dtos; using TeslaSolarCharger.Server.Dtos.FleetTelemetry; +using TeslaSolarCharger.Server.Enums; using TeslaSolarCharger.Server.Helper; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Contracts; @@ -197,9 +199,10 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str client.LastReceivedHeartbeat = dateTimeProvider.UtcNow(); continue; } - logger.LogTrace("Received non heartbeate message."); - var message = DeserializeFleetTelemetryMessage(jsonMessage); - if (message == default) + logger.LogTrace("Received non heartbeat message."); + var jObject = JObject.Parse(jsonMessage); + var messageType = jObject[nameof(FleetTelemetryMessageBase.MessageType)]?.ToObject(); + if (messageType == FleetTelemetryMessageType.Error) { var couldHandleErrorMessage = await HandleErrorMessage(jsonMessage); if (!couldHandleErrorMessage) @@ -208,7 +211,12 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str } continue; } - + var message = DeserializeFleetTelemetryMessage(jsonMessage); + if (message == default) + { + logger.LogWarning("Could not deserialize non heartbeat message {string}", jsonMessage); + continue; + } if (configurationWrapper.LogLocationData() || (message.Type != CarValueType.Latitude && message.Type != CarValueType.Longitude)) {