From 2f09bf14d96db07fe64928c50c955036da8b3980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 11:44:20 +0100 Subject: [PATCH 01/29] feat(PowerBufferComponent): do not use in memory value --- .../Components/PowerBufferComponent.razor | 21 +++++++++---------- .../Contracts/IBaseConfigurationService.cs | 2 +- .../BaseConfigurationController.cs | 5 ++++- .../Services/ApiServices/IndexService.cs | 2 +- .../Services/BaseConfigurationService.cs | 7 ++++--- .../Server/Services/ChargingService.cs | 2 +- .../Services/TscOnlyChargingCostService.cs | 2 +- .../Shared/Contracts/IConfigurationWrapper.cs | 2 +- .../BaseConfigurationBase.cs | 2 +- .../Shared/Dtos/Contracts/ISettings.cs | 1 - .../Shared/Dtos/Settings/Settings.cs | 1 - .../Shared/Wrappers/ConfigurationWrapper.cs | 10 +-------- 12 files changed, 25 insertions(+), 32 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor index 12b957940..1061076ac 100644 --- a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor +++ b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor @@ -1,15 +1,14 @@ @using TeslaSolarCharger.Shared.Dtos -@using TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues @inject HttpClient HttpClient @inject ISnackbar Snackbar -@if(_displayValue && _pvValues != default) +@if (_displayValue && PowerBuffer != default) { -
+
@@ -17,7 +16,7 @@ @code { - private DtoPvValues? _pvValues; + private int? PowerBuffer { get; set; } private bool _displayValue; protected override async Task OnInitializedAsync() @@ -30,18 +29,18 @@ _displayValue = result.Value; if(_displayValue) { - _pvValues = await HttpClient.GetFromJsonAsync("api/Index/GetPvValues").ConfigureAwait(false); + var powerBufferResult = await HttpClient.GetFromJsonAsync>("api/BaseConfiguration/PowerBuffer").ConfigureAwait(false); + if (powerBufferResult != default) + { + PowerBuffer = powerBufferResult.Value; + } } } private async Task UpdatePowerBuffer() { - if(_pvValues == default) - { - return; - } - var response = await HttpClient.GetAsync($"api/BaseConfiguration/UpdatePowerBuffer?powerBuffer={_pvValues.PowerBuffer ?? 0}").ConfigureAwait(false); + var response = await HttpClient.GetAsync($"api/BaseConfiguration/UpdatePowerBuffer?powerBuffer={PowerBuffer ?? 0}").ConfigureAwait(false); if (response.IsSuccessStatusCode) { Snackbar.Add("Power Buffer updated", Severity.Success); diff --git a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs index 30ce4c67c..a4eef337c 100644 --- a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs @@ -8,7 +8,7 @@ public interface IBaseConfigurationService { Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfiguration); Task UpdateMaxCombinedCurrent(int? maxCombinedCurrent); - void UpdatePowerBuffer(int powerBuffer); + Task UpdatePowerBuffer(int powerBuffer); Task DownloadBackup(string backupFileNamePrefix, string? backupZipDestinationDirectory); Task RestoreBackup(IFormFile file); Task CreateLocalBackupZipFile(string backupFileNamePrefix, string? backupZipDestinationDirectory, bool clearBackupDirectoryBeforeBackup); diff --git a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs index 119d579ee..09214c8b1 100644 --- a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs +++ b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs @@ -27,7 +27,10 @@ public Task UpdateMaxCombinedCurrent(int? maxCombinedCurrent) => service.UpdateMaxCombinedCurrent(maxCombinedCurrent); [HttpGet] - public void UpdatePowerBuffer(int powerBuffer) => + public DtoValue PowerBuffer() => new(configurationWrapper.PowerBuffer()); + + [HttpGet] + public Task UpdatePowerBuffer(int powerBuffer) => service.UpdatePowerBuffer(powerBuffer); [HttpGet] diff --git a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs index 61dc589df..9edd92dda 100644 --- a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs +++ b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs @@ -33,7 +33,7 @@ public class IndexService( public DtoPvValues GetPvValues() { logger.LogTrace("{method}()", nameof(GetPvValues)); - int? powerBuffer = configurationWrapper.PowerBuffer(true); + int? powerBuffer = configurationWrapper.PowerBuffer(); if (settings.InverterPower == null && settings.Overage == null) { powerBuffer = null; diff --git a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs index 520d6f392..90cf0b624 100644 --- a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs @@ -32,7 +32,6 @@ public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfigur { await teslaMateMqttService.ConnectClientIfNotConnected().ConfigureAwait(false); } - settings.PowerBuffer = null; if (restartNeeded) { @@ -47,9 +46,11 @@ public async Task UpdateMaxCombinedCurrent(int? maxCombinedCurrent) await configurationWrapper.UpdateBaseConfigurationAsync(baseConfiguration).ConfigureAwait(false); } - public void UpdatePowerBuffer(int powerBuffer) + public async Task UpdatePowerBuffer(int powerBuffer) { - settings.PowerBuffer = powerBuffer; + var config = await configurationWrapper.GetBaseConfigurationAsync(); + config.PowerBuffer = powerBuffer; + await configurationWrapper.UpdateBaseConfigurationAsync(config); } public async Task DownloadBackup(string backupFileNamePrefix, string? backupZipDestinationDirectory) diff --git a/TeslaSolarCharger/Server/Services/ChargingService.cs b/TeslaSolarCharger/Server/Services/ChargingService.cs index c1db7e426..ba6470b5f 100644 --- a/TeslaSolarCharger/Server/Services/ChargingService.cs +++ b/TeslaSolarCharger/Server/Services/ChargingService.cs @@ -188,7 +188,7 @@ public async Task CalculatePowerToControl() { logger.LogTrace("{method}()", nameof(CalculatePowerToControl)); - var buffer = configurationWrapper.PowerBuffer(true); + var buffer = configurationWrapper.PowerBuffer(); logger.LogDebug("Adding powerbuffer {powerbuffer}", buffer); var averagedOverage = settings.Overage ?? constants.DefaultOverage; logger.LogDebug("Averaged overage {averagedOverage}", averagedOverage); diff --git a/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs b/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs index 59e072c5d..85f6bbcf6 100644 --- a/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs +++ b/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs @@ -286,7 +286,7 @@ private List AddDefaultChargePrices(List prices, DateTimeOffset fr public async Task AddChargingDetailsForAllCars() { logger.LogTrace("{method}()", nameof(AddChargingDetailsForAllCars)); - var powerBuffer = configurationWrapper.PowerBuffer(true); + var powerBuffer = configurationWrapper.PowerBuffer(); var overage = settings.Overage ?? (settings.InverterPower - (powerBuffer < 0 ? 0 : powerBuffer)); var homeBatteryDischargingPower = (- settings.HomeBatteryPower) ?? 0; if (homeBatteryDischargingPower < 0) diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index 21aa9eda2..e539e8f64 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -16,7 +16,7 @@ public interface IConfigurationWrapper string GeoFence(); TimeSpan TimespanUntilSwitchOn(); TimeSpan TimespanUntilSwitchOff(); - int PowerBuffer(bool getInMemoryValueIfAvailable); + int PowerBuffer(); string? TelegramBotKey(); string? TelegramChannelId(); string? CurrentInverterPowerJsonPattern(); diff --git a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs index b54c76ea4..8a0508382 100644 --- a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs +++ b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs @@ -45,7 +45,7 @@ public class BaseConfigurationBase public int MinutesUntilSwitchOff { get; set; } = 5; [Required] public int PowerBuffer { get; set; } = 0; - [HelperText("If enabled, the configured power buffer is displayed on the home screen, including the option to directly change it. Note: The values you set on the home screen will be overwritten on every TSC restart. To set a permanent power buffer, use the field above.")] + [HelperText("If enabled, the configured power buffer is displayed on the home screen, including the option to directly change it.")] public bool AllowPowerBufferChangeOnHome { get; set; } public string? CurrentPowerToGridJsonPattern { get; set; } public decimal CurrentPowerToGridCorrectionFactor { get; set; } = 1; diff --git a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs index 1adca29ff..d26c97cbb 100644 --- a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs +++ b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs @@ -6,7 +6,6 @@ public interface ISettings { int? InverterPower { get; set; } int? Overage { get; set; } - int? PowerBuffer { get; set; } int? HomeBatterySoc { get; set; } int? HomeBatteryPower { get; set; } bool ControlledACarAtLastCycle { get; set; } diff --git a/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs b/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs index 7c9227673..e8f43835e 100644 --- a/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs +++ b/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs @@ -7,7 +7,6 @@ public class Settings : ISettings public bool IsNewVersionAvailable { get; set; } public int? InverterPower { get; set; } public int? Overage { get; set; } - public int? PowerBuffer { get; set; } public List CarsToManage => Cars.Where(c => c.ShouldBeManaged == true).OrderBy(c => c.ChargingPriority).ToList(); public int? HomeBatterySoc { get; set; } public int? HomeBatteryPower { get; set; } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index fd85b529b..e9d7e92ff 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -559,16 +559,8 @@ public TimeSpan TimespanUntilSwitchOff() return value; } - public int PowerBuffer(bool getInMemoryValueIfAvailable) + public int PowerBuffer() { - if (getInMemoryValueIfAvailable) - { - var settingsPowerBuffer = settings.PowerBuffer; - if (settingsPowerBuffer != null) - { - return settingsPowerBuffer.Value; - } - } return GetBaseConfiguration().PowerBuffer; } From b519e80c22523db2baecbdb8874e313628f43b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 13:20:15 +0100 Subject: [PATCH 02/29] feat(TeslaFleetApiService): log all values from fleet API to database --- .../Server/Services/TeslaFleetApiService.cs | 91 ++++++++++++++++++- .../Shared/Enums/CarValueType.cs | 4 + 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index d0c9103c4..dac74c785 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -323,10 +323,26 @@ public async Task RefreshCarData() await errorHandlingService.HandleErrorResolved(issueKeys.GetVehicleData, car.Vin); if (configurationWrapper.GetVehicleDataFromTesla()) { - + var timeStamp = dateTimeProvider.UtcNow(); car.Name = vehicleDataResult.VehicleState.VehicleName; car.SoC = vehicleDataResult.ChargeState.BatteryLevel; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.StateOfCharge, + Source = CarValueSource.FleetApi, + IntValue = vehicleDataResult.ChargeState.BatteryLevel, + }); car.SocLimit = vehicleDataResult.ChargeState.ChargeLimitSoc; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.StateOfChargeLimit, + Source = CarValueSource.FleetApi, + IntValue = vehicleDataResult.ChargeState.ChargeLimitSoc, + }); var minimumSettableSocLimit = vehicleDataResult.ChargeState.ChargeLimitSocMin; if (car.MinimumSoC > car.SocLimit && car.SocLimit > minimumSettableSocLimit) { @@ -335,9 +351,41 @@ public async Task RefreshCarData() logger.LogError("Can not handle lower Soc than minimumSoc"); } car.ChargerPhases = vehicleDataResult.ChargeState.ChargerPhases; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.ChargerPhases, + Source = CarValueSource.FleetApi, + IntValue = vehicleDataResult.ChargeState.ChargerPhases is null or > 1 ? 3 : 1, + }); car.ChargerVoltage = vehicleDataResult.ChargeState.ChargerVoltage; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.ChargerVoltage, + Source = CarValueSource.FleetApi, + IntValue = vehicleDataResult.ChargeState.ChargerVoltage, + }); car.ChargerActualCurrent = vehicleDataResult.ChargeState.ChargerActualCurrent; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.ChargeAmps, + Source = CarValueSource.FleetApi, + IntValue = vehicleDataResult.ChargeState.ChargerActualCurrent, + }); car.PluggedIn = vehicleDataResult.ChargeState.ChargingState != "Disconnected"; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.IsPluggedIn, + Source = CarValueSource.FleetApi, + BooleanValue = vehicleDataResult.ChargeState.ChargingState != "Disconnected", + }); car.ClimateOn = vehicleDataResult.ClimateState.IsClimateOn; car.TimeUntilFullCharge = TimeSpan.FromHours(vehicleDataResult.ChargeState.TimeToFullCharge); var teslaCarStateString = vehicleDataResult.State; @@ -345,6 +393,14 @@ public async Task RefreshCarData() var teslaCarSoftwareUpdateState = vehicleDataResult.VehicleState.SoftwareUpdate.Status; var chargingState = vehicleDataResult.ChargeState.ChargingState; car.State = DetermineCarState(teslaCarStateString, teslaCarShiftState, teslaCarSoftwareUpdateState, chargingState); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.IsCharging, + Source = CarValueSource.FleetApi, + BooleanValue = vehicleDataResult.ChargeState.ChargingState != "Charging", + }); if (car.State == CarStateEnum.Unknown) { await errorHandlingService.HandleError(nameof(TeslaFleetApiService), nameof(RefreshCarData), $"Error determining car state for car {car.Vin}", @@ -356,10 +412,43 @@ public async Task RefreshCarData() } car.Healthy = true; car.ChargerRequestedCurrent = vehicleDataResult.ChargeState.ChargeCurrentRequest; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.ChargeCurrentRequest, + Source = CarValueSource.FleetApi, + IntValue = vehicleDataResult.ChargeState.ChargeCurrentRequest, + }); car.ChargerPilotCurrent = vehicleDataResult.ChargeState.ChargerPilotCurrent; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.ChargerPilotCurrent, + Source = CarValueSource.FleetApi, + IntValue = vehicleDataResult.ChargeState.ChargerPilotCurrent, + }); car.ScheduledChargingStartTime = vehicleDataResult.ChargeState.ScheduledChargingStartTime == null ? (DateTimeOffset?)null : DateTimeOffset.FromUnixTimeSeconds(vehicleDataResult.ChargeState.ScheduledChargingStartTime.Value); car.Longitude = vehicleDataResult.DriveState.Longitude; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.Longitude, + Source = CarValueSource.FleetApi, + DoubleValue = vehicleDataResult.DriveState.Longitude, + }); car.Latitude = vehicleDataResult.DriveState.Latitude; + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = timeStamp, + Type = CarValueType.Latitude, + Source = CarValueSource.FleetApi, + DoubleValue = vehicleDataResult.DriveState.Latitude, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } await errorHandlingService.HandleErrorResolved(issueKeys.UnhandledCarStateRefresh, car.Vin); diff --git a/TeslaSolarCharger/Shared/Enums/CarValueType.cs b/TeslaSolarCharger/Shared/Enums/CarValueType.cs index 327a605fc..a1c6b4b0d 100644 --- a/TeslaSolarCharger/Shared/Enums/CarValueType.cs +++ b/TeslaSolarCharger/Shared/Enums/CarValueType.cs @@ -16,5 +16,9 @@ public enum CarValueType Location, Longitude, Latitude, + StateOfCharge, + StateOfChargeLimit, + ChargerPhases, + ChargerVoltage, Unknown = 9999, } From 868b59c830421947c386bb474a6020a487f11192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 13:24:25 +0100 Subject: [PATCH 03/29] feat(TeslaFleetApiService): log offline state to database --- .../Server/Services/TeslaFleetApiService.cs | 15 +++++++++++++++ TeslaSolarCharger/Shared/Enums/CarValueType.cs | 1 + 2 files changed, 16 insertions(+) diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index dac74c785..9a3855481 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -288,14 +288,29 @@ public async Task RefreshCarData() var vehicleState = vehicleResult.State; if (configurationWrapper.GetVehicleDataFromTesla()) { + var carStateLog = new CarValueLog() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.FleetApi, + Type = CarValueType.AsleepOrOffline, + }; if (vehicleState == "asleep") { + carStateLog.BooleanValue = true; car.State = CarStateEnum.Asleep; } else if (vehicleState == "offline") { + carStateLog.BooleanValue = true; car.State = CarStateEnum.Offline; } + else + { + carStateLog.BooleanValue = false; + } + teslaSolarChargerContext.CarValueLogs.Add(carStateLog); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } if (vehicleState is "asleep" or "offline") diff --git a/TeslaSolarCharger/Shared/Enums/CarValueType.cs b/TeslaSolarCharger/Shared/Enums/CarValueType.cs index a1c6b4b0d..ecb241efc 100644 --- a/TeslaSolarCharger/Shared/Enums/CarValueType.cs +++ b/TeslaSolarCharger/Shared/Enums/CarValueType.cs @@ -20,5 +20,6 @@ public enum CarValueType StateOfChargeLimit, ChargerPhases, ChargerVoltage, + AsleepOrOffline, Unknown = 9999, } From 56486c8efd2136960f613aaa1560676f14b7d4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 13:25:33 +0100 Subject: [PATCH 04/29] refactor(TeslaMateMqttService): use primary constructor --- .../Server/Services/TeslaMateMqttService.cs | 99 ++++++++----------- 1 file changed, 43 insertions(+), 56 deletions(-) diff --git a/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs b/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs index 8b1824dd8..58139fea7 100644 --- a/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs @@ -8,15 +8,15 @@ namespace TeslaSolarCharger.Server.Services; -public class TeslaMateMqttService : ITeslaMateMqttService +public class TeslaMateMqttService( + ILogger logger, + IMqttClient mqttClient, + MqttFactory mqttFactory, + ISettings settings, + IConfigurationWrapper configurationWrapper, + IDateTimeProvider dateTimeProvider) + : ITeslaMateMqttService { - private readonly ILogger _logger; - private readonly IMqttClient _mqttClient; - private readonly MqttFactory _mqttFactory; - private readonly ISettings _settings; - private readonly IConfigurationWrapper _configurationWrapper; - private readonly IConfigJsonService _configJsonService; - private readonly IDateTimeProvider _dateTimeProvider; // ReSharper disable once InconsistentNaming private const string TopicDisplayName = "display_name"; @@ -53,49 +53,36 @@ public class TeslaMateMqttService : ITeslaMateMqttService // ReSharper disable once InconsistentNaming private const string TopicSpeed = "speed"; - public bool IsMqttClientConnected => _mqttClient.IsConnected; - - public TeslaMateMqttService(ILogger logger, IMqttClient mqttClient, MqttFactory mqttFactory, - ISettings settings, IConfigurationWrapper configurationWrapper, - IConfigJsonService configJsonService, IDateTimeProvider dateTimeProvider) - { - _logger = logger; - _mqttClient = mqttClient; - _mqttFactory = mqttFactory; - _settings = settings; - _configurationWrapper = configurationWrapper; - _configJsonService = configJsonService; - _dateTimeProvider = dateTimeProvider; - } + public bool IsMqttClientConnected => mqttClient.IsConnected; public async Task ConnectMqttClient() { - _logger.LogTrace("{method}()", nameof(ConnectMqttClient)); + logger.LogTrace("{method}()", nameof(ConnectMqttClient)); var guid = Guid.NewGuid(); - var mqqtClientId = _configurationWrapper.MqqtClientId() + guid; - var mosquitoServer = _configurationWrapper.MosquitoServer(); + var mqqtClientId = configurationWrapper.MqqtClientId() + guid; + var mosquitoServer = configurationWrapper.MosquitoServer(); var mqttClientOptions = new MqttClientOptionsBuilder() .WithClientId(mqqtClientId) .WithTcpServer(mosquitoServer) .Build(); - _mqttClient.ApplicationMessageReceivedAsync += e => + mqttClient.ApplicationMessageReceivedAsync += e => { var value = GetValueFromMessage(e.ApplicationMessage); - if ((!_configurationWrapper.LogLocationData()) && (string.Equals(value.Topic, TopicLongitude) || string.Equals(value.Topic, TopicLatitude))) + if ((!configurationWrapper.LogLocationData()) && (string.Equals(value.Topic, TopicLongitude) || string.Equals(value.Topic, TopicLatitude))) { - _logger.LogTrace("Car Id: {carId}, Topic: {topic}, Value: xx.xxxxx", value.CarId, value.Topic); + logger.LogTrace("Car Id: {carId}, Topic: {topic}, Value: xx.xxxxx", value.CarId, value.Topic); } else { - _logger.LogTrace("Car Id: {carId}, Topic: {topic}, Value: {value}", value.CarId, value.Topic, value.Value); + logger.LogTrace("Car Id: {carId}, Topic: {topic}, Value: {value}", value.CarId, value.Topic, value.Value); } UpdateCar(value); return Task.CompletedTask; }; - if (_mqttClient.IsConnected) + if (mqttClient.IsConnected) { await DisconnectClient("Reconnecting with new configuration").ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); @@ -103,17 +90,17 @@ public async Task ConnectMqttClient() try { - await _mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None).ConfigureAwait(false); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { - _logger.LogError(ex, "Could not connect to TeslaMate mqtt server"); + logger.LogError(ex, "Could not connect to TeslaMate mqtt server"); return; } var topicPrefix = "teslamate/cars/+/"; - var mqttSubscribeOptions = _mqttFactory.CreateSubscribeOptionsBuilder() + var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() .WithTopicFilter(f => { f.WithTopic($"{topicPrefix}{TopicDisplayName}"); @@ -184,46 +171,46 @@ public async Task ConnectMqttClient() }) .Build(); - await _mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None).ConfigureAwait(false); + await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None).ConfigureAwait(false); } public async Task DisconnectClient(string reason) { - _logger.LogTrace("{method}({reason})", nameof(DisconnectClient), reason); - if (_mqttClient.IsConnected) + logger.LogTrace("{method}({reason})", nameof(DisconnectClient), reason); + if (mqttClient.IsConnected) { - await _mqttClient.DisconnectAsync().ConfigureAwait(false); + await mqttClient.DisconnectAsync().ConfigureAwait(false); } } public async Task ConnectClientIfNotConnected() { - _logger.LogTrace("{method}()", nameof(ConnectClientIfNotConnected)); - if (_mqttClient.IsConnected) + logger.LogTrace("{method}()", nameof(ConnectClientIfNotConnected)); + if (mqttClient.IsConnected) { - _logger.LogTrace("MqttClient is connected"); + logger.LogTrace("MqttClient is connected"); return; } - if (_configurationWrapper.GetVehicleDataFromTesla()) + if (configurationWrapper.GetVehicleDataFromTesla()) { - _logger.LogInformation("Not connecting to TeslaMate as data is retrieved from Teslas Fleet API"); + logger.LogInformation("Not connecting to TeslaMate as data is retrieved from Teslas Fleet API"); return; } - _logger.LogWarning("MqttClient is not connected"); + logger.LogWarning("MqttClient is not connected"); await ConnectMqttClient().ConfigureAwait(false); } internal void UpdateCar(TeslaMateValue value) { - _logger.LogTrace("{method}({@param})", nameof(UpdateCar), value); - var car = _settings.Cars.FirstOrDefault(c => c.TeslaMateCarId == value.CarId); + logger.LogTrace("{method}({@param})", nameof(UpdateCar), value); + var car = settings.Cars.FirstOrDefault(c => c.TeslaMateCarId == value.CarId); if (car == null) { // Logge einen Fehler oder handle den Fall, dass kein Auto gefunden wurde - _logger.LogError($"No car found with TeslaMateCarId {value.CarId}"); + logger.LogError($"No car found with TeslaMateCarId {value.CarId}"); return; // oder andere geeignete Maßnahme } @@ -249,9 +236,9 @@ internal void UpdateCar(TeslaMateValue value) var minimumSettableSocLimit = 50; if (car.MinimumSoC > car.SocLimit && car.SocLimit > minimumSettableSocLimit) { - _logger.LogWarning("Reduce Minimum SoC {minimumSoC} as charge limit {chargeLimit} is lower.", car.MinimumSoC, car.SocLimit); + logger.LogWarning("Reduce Minimum SoC {minimumSoC} as charge limit {chargeLimit} is lower.", car.MinimumSoC, car.SocLimit); car.MinimumSoC = (int)car.SocLimit; - _logger.LogError("Can not handle lower Soc than minimumSoc"); + logger.LogError("Can not handle lower Soc than minimumSoc"); } } break; @@ -263,7 +250,7 @@ internal void UpdateCar(TeslaMateValue value) else { //This is needed as TeslaMate sometime sends empty values during charger being connected. - _logger.LogDebug($"{nameof(TopicChargerPhases)} is {value.Value}. Do not overwrite charger phases."); + logger.LogDebug($"{nameof(TopicChargerPhases)} is {value.Value}. Do not overwrite charger phases."); //car.CarState.ChargerPhases = null; } break; @@ -286,14 +273,14 @@ internal void UpdateCar(TeslaMateValue value) car.LastSetAmp == car.ChargerActualCurrent - 1 && car.LastSetAmp > 0) { - _logger.LogWarning("CarId {carId}: Reducing {actualCurrent} from {originalValue} to {newValue} due to error in TeslaApi", car.Id, nameof(car.ChargerActualCurrent), car.ChargerActualCurrent, car.LastSetAmp); + logger.LogWarning("CarId {carId}: Reducing {actualCurrent} from {originalValue} to {newValue} due to error in TeslaApi", car.Id, nameof(car.ChargerActualCurrent), car.ChargerActualCurrent, car.LastSetAmp); //ToDo: Set to average of requested and actual current car.ChargerActualCurrent = car.LastSetAmp; } if (car.ChargerActualCurrent > 0 && car.PluggedIn != true) { - _logger.LogWarning("Car {carId} is not detected as plugged in but actual current > 0 => set plugged in to true", car.Id); + logger.LogWarning("Car {carId} is not detected as plugged in but actual current > 0 => set plugged in to true", car.Id); car.PluggedIn = true; } } @@ -349,14 +336,14 @@ internal void UpdateCar(TeslaMateValue value) car.State = CarStateEnum.Updating; break; default: - _logger.LogWarning("Unknown car state deteckted: {carState}", value.Value); + logger.LogWarning("Unknown car state deteckted: {carState}", value.Value); car.State = CarStateEnum.Unknown; break; } break; case TopicHealthy: car.Healthy = Convert.ToBoolean(value.Value); - _logger.LogTrace("Car healthiness if car {carId} changed to {healthiness}", car.Id, car.Healthy); + logger.LogTrace("Car healthiness if car {carId} changed to {healthiness}", car.Id, car.Healthy); break; case TopicChargeCurrentRequest: if (!string.IsNullOrWhiteSpace(value.Value)) @@ -371,13 +358,13 @@ internal void UpdateCar(TeslaMateValue value) } break; case TopicScheduledChargingStartTime: - _logger.LogTrace("{topicName} changed to {value}", nameof(TopicScheduledChargingStartTime), value.Value); + logger.LogTrace("{topicName} changed to {value}", nameof(TopicScheduledChargingStartTime), value.Value); if (!string.IsNullOrWhiteSpace(value.Value)) { var parsedScheduledChargingStartTime = DateTimeOffset.Parse(value.Value); - if (parsedScheduledChargingStartTime < _dateTimeProvider.DateTimeOffSetNow().AddDays(-14)) + if (parsedScheduledChargingStartTime < dateTimeProvider.DateTimeOffSetNow().AddDays(-14)) { - _logger.LogWarning("TeslaMate set scheduled charging start time to {teslaMateValue}. As this is in the past, it will be ignored.", parsedScheduledChargingStartTime); + logger.LogWarning("TeslaMate set scheduled charging start time to {teslaMateValue}. As this is in the past, it will be ignored.", parsedScheduledChargingStartTime); car.ScheduledChargingStartTime = null; } else From f3d63819afe8efd2a15548d61e3bb2327a74f0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 14:17:43 +0100 Subject: [PATCH 05/29] feat(PowerBufferComponent): only enable save on changed value --- .../Client/Components/PowerBufferComponent.razor | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor index 1061076ac..f1f3980a0 100644 --- a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor +++ b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor @@ -9,7 +9,10 @@
} @@ -17,6 +20,7 @@ @code { private int? PowerBuffer { get; set; } + private int? _lastSavedPowerBuffer; private bool _displayValue; protected override async Task OnInitializedAsync() @@ -32,6 +36,7 @@ var powerBufferResult = await HttpClient.GetFromJsonAsync>("api/BaseConfiguration/PowerBuffer").ConfigureAwait(false); if (powerBufferResult != default) { + _lastSavedPowerBuffer = powerBufferResult.Value; PowerBuffer = powerBufferResult.Value; } } @@ -44,6 +49,7 @@ if (response.IsSuccessStatusCode) { Snackbar.Add("Power Buffer updated", Severity.Success); + _lastSavedPowerBuffer = PowerBuffer; } else { From ef6c0308853cb69395cd84d25cc5c5b174c4d383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 14:58:24 +0100 Subject: [PATCH 06/29] feat(TeslaMateMqttService): log new data to varValueLogs --- .../Server/Services/TeslaMateMqttService.cs | 132 +++++++++++++++++- .../Shared/Enums/CarValueSource.cs | 1 + 2 files changed, 128 insertions(+), 5 deletions(-) diff --git a/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs b/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs index 58139fea7..9445e2c06 100644 --- a/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs @@ -1,6 +1,8 @@ using MQTTnet; using MQTTnet.Client; using System.Globalization; +using TeslaSolarCharger.Model.Contracts; +using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; using TeslaSolarCharger.Server.Contracts; using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.Dtos.Contracts; @@ -14,7 +16,8 @@ public class TeslaMateMqttService( MqttFactory mqttFactory, ISettings settings, IConfigurationWrapper configurationWrapper, - IDateTimeProvider dateTimeProvider) + IDateTimeProvider dateTimeProvider, + ITeslaSolarChargerContext teslaSolarChargerContext) : ITeslaMateMqttService { @@ -66,7 +69,7 @@ public async Task ConnectMqttClient() .WithTcpServer(mosquitoServer) .Build(); - mqttClient.ApplicationMessageReceivedAsync += e => + mqttClient.ApplicationMessageReceivedAsync += async e => { var value = GetValueFromMessage(e.ApplicationMessage); if ((!configurationWrapper.LogLocationData()) && (string.Equals(value.Topic, TopicLongitude) || string.Equals(value.Topic, TopicLatitude))) @@ -77,8 +80,7 @@ public async Task ConnectMqttClient() { logger.LogTrace("Car Id: {carId}, Topic: {topic}, Value: {value}", value.CarId, value.Topic, value.Value); } - UpdateCar(value); - return Task.CompletedTask; + await UpdateCar(value); }; @@ -202,7 +204,7 @@ public async Task ConnectClientIfNotConnected() await ConnectMqttClient().ConfigureAwait(false); } - internal void UpdateCar(TeslaMateValue value) + internal async Task UpdateCar(TeslaMateValue value) { logger.LogTrace("{method}({@param})", nameof(UpdateCar), value); var car = settings.Cars.FirstOrDefault(c => c.TeslaMateCarId == value.CarId); @@ -227,12 +229,30 @@ internal void UpdateCar(TeslaMateValue value) if (!string.IsNullOrWhiteSpace(value.Value)) { car.SoC = Convert.ToInt32(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.StateOfCharge, + IntValue = car.SoC, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } break; case TopicChargeLimit: if (!string.IsNullOrWhiteSpace(value.Value)) { car.SocLimit = Convert.ToInt32(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.StateOfChargeLimit, + IntValue = car.SocLimit, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); var minimumSettableSocLimit = 50; if (car.MinimumSoC > car.SocLimit && car.SocLimit > minimumSettableSocLimit) { @@ -246,6 +266,15 @@ internal void UpdateCar(TeslaMateValue value) if (!string.IsNullOrWhiteSpace(value.Value)) { car.ChargerPhases = Convert.ToInt32(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.ChargerPhases, + IntValue = car.ChargerPhases is null or > 1 ? 3 : 1, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } else { @@ -258,6 +287,15 @@ internal void UpdateCar(TeslaMateValue value) if (!string.IsNullOrWhiteSpace(value.Value)) { car.ChargerVoltage = Convert.ToInt32(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.ChargerVoltage, + IntValue = car.ChargerVoltage, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } else { @@ -268,6 +306,15 @@ internal void UpdateCar(TeslaMateValue value) if (!string.IsNullOrWhiteSpace(value.Value)) { car.ChargerActualCurrent = Convert.ToInt32(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.ChargeAmps, + IntValue = car.ChargerActualCurrent, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); if (car.ChargerActualCurrent < 5 && car.ChargerRequestedCurrent == car.LastSetAmp && car.LastSetAmp == car.ChargerActualCurrent - 1 && @@ -293,6 +340,15 @@ internal void UpdateCar(TeslaMateValue value) if (!string.IsNullOrWhiteSpace(value.Value)) { car.PluggedIn = Convert.ToBoolean(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.IsPluggedIn, + BooleanValue = car.PluggedIn == true, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } break; case TopicIsClimateOn: @@ -312,34 +368,64 @@ internal void UpdateCar(TeslaMateValue value) } break; case TopicState: + var asleepValueLog = new CarValueLog() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.AsleepOrOffline, + }; + var chargingValueLog = new CarValueLog() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.IsCharging, + }; switch (value.Value) { case "asleep": car.State = CarStateEnum.Asleep; + asleepValueLog.BooleanValue = true; + chargingValueLog.BooleanValue = false; break; case "offline": car.State = CarStateEnum.Offline; + asleepValueLog.BooleanValue = true; + chargingValueLog.BooleanValue = false; break; case "online": car.State = CarStateEnum.Online; + asleepValueLog.BooleanValue = false; + chargingValueLog.BooleanValue = false; break; case "charging": car.State = CarStateEnum.Charging; + asleepValueLog.BooleanValue = false; + chargingValueLog.BooleanValue = true; break; case "suspended": car.State = CarStateEnum.Suspended; + asleepValueLog.BooleanValue = false; + chargingValueLog.BooleanValue = false; break; case "driving": car.State = CarStateEnum.Driving; + asleepValueLog.BooleanValue = false; + chargingValueLog.BooleanValue = false; break; case "updating": car.State = CarStateEnum.Updating; + asleepValueLog.BooleanValue = false; break; default: logger.LogWarning("Unknown car state deteckted: {carState}", value.Value); car.State = CarStateEnum.Unknown; break; } + teslaSolarChargerContext.CarValueLogs.Add(asleepValueLog); + teslaSolarChargerContext.CarValueLogs.Add(chargingValueLog); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); break; case TopicHealthy: car.Healthy = Convert.ToBoolean(value.Value); @@ -349,12 +435,30 @@ internal void UpdateCar(TeslaMateValue value) if (!string.IsNullOrWhiteSpace(value.Value)) { car.ChargerRequestedCurrent = Convert.ToInt32(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.ChargeCurrentRequest, + IntValue = car.ChargerRequestedCurrent, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } break; case TopicChargeCurrentRequestMax: if (!string.IsNullOrWhiteSpace(value.Value)) { car.ChargerPilotCurrent = Convert.ToInt32(value.Value); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.ChargerPilotCurrent, + IntValue = car.ChargerPilotCurrent, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } break; case TopicScheduledChargingStartTime: @@ -381,12 +485,30 @@ internal void UpdateCar(TeslaMateValue value) if (!string.IsNullOrWhiteSpace(value.Value)) { car.Longitude = Convert.ToDouble(value.Value, CultureInfo.InvariantCulture); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.Longitude, + DoubleValue = car.Longitude, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } break; case TopicLatitude: if (!string.IsNullOrWhiteSpace(value.Value)) { car.Latitude = Convert.ToDouble(value.Value, CultureInfo.InvariantCulture); + teslaSolarChargerContext.CarValueLogs.Add(new() + { + CarId = car.Id, + Timestamp = dateTimeProvider.UtcNow(), + Source = CarValueSource.TeslaMate, + Type = CarValueType.Latitude, + DoubleValue = car.Latitude, + }); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } break; case TopicSpeed: diff --git a/TeslaSolarCharger/Shared/Enums/CarValueSource.cs b/TeslaSolarCharger/Shared/Enums/CarValueSource.cs index 57d14690c..4db3dbfda 100644 --- a/TeslaSolarCharger/Shared/Enums/CarValueSource.cs +++ b/TeslaSolarCharger/Shared/Enums/CarValueSource.cs @@ -4,4 +4,5 @@ public enum CarValueSource { FleetTelemetry, FleetApi, + TeslaMate, } From 697174d4c6be9bc2b17471a8577d88d44ef3ebea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 15:08:09 +0100 Subject: [PATCH 07/29] fix(PowerBufferComponent): display powerBuffer on null values --- TeslaSolarCharger/Client/Components/PowerBufferComponent.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor index f1f3980a0..323cb047f 100644 --- a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor +++ b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor @@ -4,7 +4,7 @@ @inject ISnackbar Snackbar -@if (_displayValue && PowerBuffer != default) +@if (_displayValue) {
Date: Sat, 9 Nov 2024 15:18:06 +0100 Subject: [PATCH 08/29] fix(PowerBufferComponent): do not allow saving null --- TeslaSolarCharger/Client/Components/PowerBufferComponent.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor index 323cb047f..72ddd5938 100644 --- a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor +++ b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor @@ -11,7 +11,7 @@ For="() => PowerBuffer" ImmediateValueUpdate="true" PostfixButtonStartIcon="@Icons.Material.Filled.Save" - IsButtonDisabled="@(PowerBuffer == _lastSavedPowerBuffer)" + IsButtonDisabled="@(PowerBuffer == _lastSavedPowerBuffer || PowerBuffer == default)" OnValueChanged="_ => InvokeAsync(StateHasChanged)" OnButtonClicked="UpdatePowerBuffer">
From cb8060de68dc4c0947cb883052fb48a5db9b654d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 20:18:21 +0100 Subject: [PATCH 09/29] feat(chore): make app compatible to MudBlazor 7.x --- .../Client/Components/BackupComponent.razor | 13 ++++++++----- .../GenericValueConfigurationComponent.razor | 2 +- .../ModbusValueConfigurationComponent.razor | 2 +- .../MqttValueConfigurationComponent.razor | 2 +- .../RestValueConfigurationComponent.razor | 2 +- TeslaSolarCharger/Client/Shared/MainLayout.razor | 3 ++- .../Client/TeslaSolarCharger.Client.csproj | 4 ++-- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/BackupComponent.razor b/TeslaSolarCharger/Client/Components/BackupComponent.razor index a0e818ee3..76a21de12 100644 --- a/TeslaSolarCharger/Client/Components/BackupComponent.razor +++ b/TeslaSolarCharger/Client/Components/BackupComponent.razor @@ -33,15 +33,14 @@
- + + StartIcon="@Icons.Material.Filled.AttachFile"> Select Backup File - +
@@ -107,8 +106,12 @@ StateHasChanged(); } - private void SelectFile(IBrowserFile file) + private void SelectFile(IBrowserFile? file) { + if(file == default) + { + return; + } if (file.Size > _maxFileSize) { Snackbar.Add($"{file.Name} is greater than {_maxFileSize / 1024 / 1024} and won't be uploaded." diff --git a/TeslaSolarCharger/Client/Components/GenericValueConfigurationComponent.razor b/TeslaSolarCharger/Client/Components/GenericValueConfigurationComponent.razor index bb6f5d92a..52973a846 100644 --- a/TeslaSolarCharger/Client/Components/GenericValueConfigurationComponent.razor +++ b/TeslaSolarCharger/Client/Components/GenericValueConfigurationComponent.razor @@ -84,7 +84,7 @@ }
- @(restValueResult.CalculatedValue == null ? "Not available" : Math.Round(restValueResult.CalculatedValue.Value, 2) + $" {suffixString}") + @(restValueResult.CalculatedValue == null ? "Not available" : Math.Round(restValueResult.CalculatedValue.Value, 2) + $" {suffixString}")
} diff --git a/TeslaSolarCharger/Client/Components/ModbusValueConfigurationComponent.razor b/TeslaSolarCharger/Client/Components/ModbusValueConfigurationComponent.razor index 01342e82e..89a10f9e1 100644 --- a/TeslaSolarCharger/Client/Components/ModbusValueConfigurationComponent.razor +++ b/TeslaSolarCharger/Client/Components/ModbusValueConfigurationComponent.razor @@ -32,7 +32,7 @@ { CloseButton = true, CloseOnEscapeKey = false, - DisableBackdropClick = true, + BackdropClick = false, }; var parameters = new DialogParameters { diff --git a/TeslaSolarCharger/Client/Components/MqttValueConfigurationComponent.razor b/TeslaSolarCharger/Client/Components/MqttValueConfigurationComponent.razor index 66beed95a..3610f40a4 100644 --- a/TeslaSolarCharger/Client/Components/MqttValueConfigurationComponent.razor +++ b/TeslaSolarCharger/Client/Components/MqttValueConfigurationComponent.razor @@ -32,7 +32,7 @@ { CloseButton = true, CloseOnEscapeKey = false, - DisableBackdropClick = true, + BackdropClick = true, }; var parameters = new DialogParameters { diff --git a/TeslaSolarCharger/Client/Components/RestValueConfigurationComponent.razor b/TeslaSolarCharger/Client/Components/RestValueConfigurationComponent.razor index 72a8db578..528044f17 100644 --- a/TeslaSolarCharger/Client/Components/RestValueConfigurationComponent.razor +++ b/TeslaSolarCharger/Client/Components/RestValueConfigurationComponent.razor @@ -34,7 +34,7 @@ { CloseButton = true, CloseOnEscapeKey = false, - DisableBackdropClick = true, + BackdropClick = true, }; var parameters = new DialogParameters { diff --git a/TeslaSolarCharger/Client/Shared/MainLayout.razor b/TeslaSolarCharger/Client/Shared/MainLayout.razor index 5f0f55851..762c06a55 100644 --- a/TeslaSolarCharger/Client/Shared/MainLayout.razor +++ b/TeslaSolarCharger/Client/Shared/MainLayout.razor @@ -7,6 +7,7 @@ @inject HttpClient HttpClient + @@ -36,7 +37,7 @@ readonly MudTheme _tscTheme = new MudTheme() { - Palette = new PaletteLight() + PaletteLight = new PaletteLight() { Primary = "#1b6ec2", Secondary = "#6c757d", diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index 9c16febf7..d1764cca6 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -23,12 +23,12 @@ - + - + From 10894ec8cd3e117ce24371fa00d6b2b5a9441dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 21:39:01 +0100 Subject: [PATCH 10/29] feat(TimerSeriesChartRazor): can display min module temp --- .../Client/Pages/HandledChargesList.razor | 13 +++- .../Client/Pages/TimeSeriesChart.razor | 61 +++++++++++++++++++ .../Controllers/TimeSeriesDataController.cs | 14 +++++ .../Server/ServiceCollectionExtensions.cs | 1 + .../Contracts/ITimeSeriesDataService.cs | 9 +++ .../Server/Services/TimeSeriesDataService.cs | 40 ++++++++++++ .../Services/TscOnlyChargingCostService.cs | 1 + .../Dtos/ChargingCost/DtoHandledCharge.cs | 3 +- .../Dtos/TimeSeries/DtoTimeSeriesDatum.cs | 9 +++ .../Shared/TeslaSolarCharger.Shared.csproj | 1 + 10 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor create mode 100644 TeslaSolarCharger/Server/Controllers/TimeSeriesDataController.cs create mode 100644 TeslaSolarCharger/Server/Services/Contracts/ITimeSeriesDataService.cs create mode 100644 TeslaSolarCharger/Server/Services/TimeSeriesDataService.cs create mode 100644 TeslaSolarCharger/Shared/Dtos/TimeSeries/DtoTimeSeriesDatum.cs diff --git a/TeslaSolarCharger/Client/Pages/HandledChargesList.razor b/TeslaSolarCharger/Client/Pages/HandledChargesList.razor index 50c722802..bf1bd5d54 100644 --- a/TeslaSolarCharger/Client/Pages/HandledChargesList.razor +++ b/TeslaSolarCharger/Client/Pages/HandledChargesList.razor @@ -18,7 +18,18 @@ else Height="@datagridHeight" Breakpoint="Breakpoint.None"> + Title="@StringHelper.GenerateFriendlyStringWithOutIdSuffix(nameof(DtoHandledCharge.StartTime))"> + + @if(context.Item.EndTime.HasValue) + { + @context.Item.StartTime + } + else + { + @context.Item.StartTime + } + + diff --git a/TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor b/TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor new file mode 100644 index 000000000..b480d3273 --- /dev/null +++ b/TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor @@ -0,0 +1,61 @@ +@page "/TimeSeries/{carId:int}/{startEpoch:long}/{endEpoch:long}" + +@using MudBlazor.Components.Chart.Models +@using TeslaSolarCharger.Shared.Dtos.TimeSeries +@using TeslaSolarCharger.Shared.Enums +@using TeslaSolarCharger.Shared.Helper.Contracts +@inject HttpClient Http +@inject IStringHelper StringHelper + +
+ +
+ +@code +{ + [Parameter] + public int CarId { get; set; } + [Parameter] + public long StartEpoch { get; set; } + [Parameter] + public long EndEpoch { get; set; } + + + private readonly ChartOptions _options = new ChartOptions + { + YAxisLines = false, + YAxisRequireZeroPoint = true, + XAxisLines = false, + LineStrokeWidth = 1, + }; + + private readonly List _series = new(); + + protected override async Task OnParametersSetAsync() + { + await LoadTimeSeriesData(); + } + + private async Task LoadTimeSeriesData() + { + var response = await Http.GetFromJsonAsync>($"api/TimeSeriesData/GetTimeSeriesData?carId={CarId}&startEpoch={StartEpoch}&endEpoch={EndEpoch}&carValueType={CarValueType.ModuleTempMin}"); + if (response != null && response.Count > 0) + { + var chartSeries = new TimeSeriesChartSeries + { + Index = 0, + Name = StringHelper.GenerateFriendlyStringFromPascalString(nameof(CarValueType.ModuleTempMin)), + Data = response.Select(d => new TimeSeriesChartSeries.TimeValue(d.Timestamp, d.Value ?? 0)).ToList(), + IsVisible = true, + Type = TimeSeriesDiplayType.Line, + }; + _series.Add(chartSeries); + + StateHasChanged(); + } + } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Server/Controllers/TimeSeriesDataController.cs b/TeslaSolarCharger/Server/Controllers/TimeSeriesDataController.cs new file mode 100644 index 000000000..2a4161b55 --- /dev/null +++ b/TeslaSolarCharger/Server/Controllers/TimeSeriesDataController.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; +using TeslaSolarCharger.Server.Services.Contracts; +using TeslaSolarCharger.Shared.Dtos.TimeSeries; +using TeslaSolarCharger.Shared.Enums; +using TeslaSolarCharger.SharedBackend.Abstracts; + +namespace TeslaSolarCharger.Server.Controllers; + +public class TimeSeriesDataController(ITimeSeriesDataService service) : ApiBaseController +{ + [HttpGet] + public Task> GetTimeSeriesData(int carId, long startEpoch, long endEpoch, CarValueType carValueType) => + service.GetTimeSeriesData(carId, startEpoch, endEpoch, carValueType); +} diff --git a/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs b/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs index 8bd30eac0..9ea7ce85b 100644 --- a/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs +++ b/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs @@ -114,6 +114,7 @@ public static IServiceCollection AddMyDependencies(this IServiceCollection servi .AddTransient() .AddTransient() .AddSingleton() + .AddSingleton() .AddSharedBackendDependencies(); return services; } diff --git a/TeslaSolarCharger/Server/Services/Contracts/ITimeSeriesDataService.cs b/TeslaSolarCharger/Server/Services/Contracts/ITimeSeriesDataService.cs new file mode 100644 index 000000000..0399bb53d --- /dev/null +++ b/TeslaSolarCharger/Server/Services/Contracts/ITimeSeriesDataService.cs @@ -0,0 +1,9 @@ +using TeslaSolarCharger.Shared.Dtos.TimeSeries; +using TeslaSolarCharger.Shared.Enums; + +namespace TeslaSolarCharger.Server.Services.Contracts; + +public interface ITimeSeriesDataService +{ + Task> GetTimeSeriesData(int carId, long startEpoch, long endEpoch, CarValueType carValueType); +} diff --git a/TeslaSolarCharger/Server/Services/TimeSeriesDataService.cs b/TeslaSolarCharger/Server/Services/TimeSeriesDataService.cs new file mode 100644 index 000000000..440a11bb7 --- /dev/null +++ b/TeslaSolarCharger/Server/Services/TimeSeriesDataService.cs @@ -0,0 +1,40 @@ +using AutoMapper.QueryableExtensions; +using Microsoft.EntityFrameworkCore; +using TeslaSolarCharger.Model.Contracts; +using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; +using TeslaSolarCharger.Server.Services.Contracts; +using TeslaSolarCharger.Shared.Dtos.TimeSeries; +using TeslaSolarCharger.Shared.Enums; +using TeslaSolarCharger.SharedBackend.MappingExtensions; + +namespace TeslaSolarCharger.Server.Services; + +public class TimeSeriesDataService(ILogger logger, + ITeslaSolarChargerContext teslaSolarChargerContext, + IMapperConfigurationFactory mapperConfigurationFactory) : ITimeSeriesDataService +{ + public async Task> GetTimeSeriesData(int carId, long startEpoch, long endEpoch, CarValueType carValueType) + { + logger.LogTrace("{method}({carId}, {startEpoch}, {endEpoch}, {carValueType})", nameof(GetTimeSeriesData), carId, startEpoch, endEpoch, carValueType); + var startDate = DateTimeOffset.FromUnixTimeSeconds(startEpoch).DateTime; + var endDate = DateTimeOffset.FromUnixTimeSeconds(endEpoch).DateTime; + + var mapper = mapperConfigurationFactory.Create(cfg => + { + cfg.CreateMap() + .ForMember(d => d.Timestamp, opt => opt.MapFrom(c => c.Timestamp.ToLocalTime())) + .ForMember(d => d.Value, opt => opt.MapFrom(c => c.IntValue ?? c.DoubleValue)) + ; + }); + + var result = await teslaSolarChargerContext.CarValueLogs + .Where(c => c.CarId == carId) + .Where(c => c.Timestamp >= startDate && c.Timestamp <= endDate) + .Where(c => c.Type == carValueType) + .OrderBy(c => c.Timestamp) + .ProjectTo(mapper) + .ToListAsync(); + + return result; + } +} diff --git a/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs b/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs index 85f6bbcf6..15970487a 100644 --- a/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs +++ b/TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs @@ -113,6 +113,7 @@ public async Task> GetFinalizedChargingProcesses(int carI //ToDo: Maybe possible null exceptions as not all members that are nullable in database are also nullable in dto cfg.CreateMap() .ForMember(d => d.StartTime, opt => opt.MapFrom(h => h.StartDate.ToLocalTime())) + .ForMember(d => d.EndTime, opt => opt.MapFrom(h => h.EndDate.HasValue ? h.EndDate.Value.ToLocalTime() : (DateTime?)null)) .ForMember(d => d.CalculatedPrice, opt => opt.MapFrom(h => h.Cost == null ? 0m : Math.Round(h.Cost.Value, 2))) .ForMember(d => d.UsedGridEnergy, opt => opt.MapFrom(h => h.UsedGridEnergyKwh == null ? 0m : Math.Round(h.UsedGridEnergyKwh.Value, 2))) .ForMember(d => d.UsedHomeBatteryEnergy, opt => opt.MapFrom(h => h.UsedHomeBatteryEnergyKwh == null ? 0m : Math.Round(h.UsedHomeBatteryEnergyKwh.Value, 2))) diff --git a/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoHandledCharge.cs b/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoHandledCharge.cs index 207b3353d..5553560d7 100644 --- a/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoHandledCharge.cs +++ b/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoHandledCharge.cs @@ -2,7 +2,8 @@ public class DtoHandledCharge { - public DateTime? StartTime { get; set; } + public DateTime StartTime { get; set; } + public DateTime? EndTime { get; set; } public decimal CalculatedPrice { get; set; } public decimal PricePerKwh { get; set; } public decimal UsedGridEnergy { get; set; } diff --git a/TeslaSolarCharger/Shared/Dtos/TimeSeries/DtoTimeSeriesDatum.cs b/TeslaSolarCharger/Shared/Dtos/TimeSeries/DtoTimeSeriesDatum.cs new file mode 100644 index 000000000..d84e62921 --- /dev/null +++ b/TeslaSolarCharger/Shared/Dtos/TimeSeries/DtoTimeSeriesDatum.cs @@ -0,0 +1,9 @@ +using TeslaSolarCharger.Shared.Enums; + +namespace TeslaSolarCharger.Shared.Dtos.TimeSeries; + +public class DtoTimeSeriesDatum +{ + public DateTime Timestamp { get; set; } + public double? Value { get; set; } +} diff --git a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj index a668c6612..286cb068d 100644 --- a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj +++ b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj @@ -12,6 +12,7 @@ + From 42283e502a53ae9e836ab8c77510780bb3b5d6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 9 Nov 2024 22:34:37 +0100 Subject: [PATCH 11/29] feat(TimeSeriesData): add more charts --- .../Client/Pages/TimeSeriesChart.razor | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor b/TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor index b480d3273..d63153dc0 100644 --- a/TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor +++ b/TeslaSolarCharger/Client/Pages/TimeSeriesChart.razor @@ -42,20 +42,28 @@ private async Task LoadTimeSeriesData() { - var response = await Http.GetFromJsonAsync>($"api/TimeSeriesData/GetTimeSeriesData?carId={CarId}&startEpoch={StartEpoch}&endEpoch={EndEpoch}&carValueType={CarValueType.ModuleTempMin}"); + await AddChartSeries(CarValueType.ModuleTempMin).ConfigureAwait(false); + await AddChartSeries(CarValueType.ModuleTempMax).ConfigureAwait(false); + await AddChartSeries(CarValueType.StateOfCharge).ConfigureAwait(false); + await AddChartSeries(CarValueType.StateOfChargeLimit).ConfigureAwait(false); + + StateHasChanged(); + } + + private async Task AddChartSeries(CarValueType carValueType) + { + var response = await Http.GetFromJsonAsync>($"api/TimeSeriesData/GetTimeSeriesData?carId={CarId}&startEpoch={StartEpoch}&endEpoch={EndEpoch}&carValueType={carValueType}"); if (response != null && response.Count > 0) { var chartSeries = new TimeSeriesChartSeries { Index = 0, - Name = StringHelper.GenerateFriendlyStringFromPascalString(nameof(CarValueType.ModuleTempMin)), + Name = StringHelper.GenerateFriendlyStringFromPascalString(carValueType.ToString()), Data = response.Select(d => new TimeSeriesChartSeries.TimeValue(d.Timestamp, d.Value ?? 0)).ToList(), IsVisible = true, Type = TimeSeriesDiplayType.Line, }; _series.Add(chartSeries); - - StateHasChanged(); } } } \ No newline at end of file From 35b15502c4f4c74b356da9c0d283407b08fe6ef2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 03:13:05 +0000 Subject: [PATCH 12/29] build(deps): bump FluentAssertions from 6.12.1 to 6.12.2 Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.12.1 to 6.12.2. - [Release notes](https://github.com/fluentassertions/fluentassertions/releases) - [Changelog](https://github.com/fluentassertions/fluentassertions/blob/develop/AcceptApiChanges.ps1) - [Commits](https://github.com/fluentassertions/fluentassertions/compare/6.12.1...6.12.2) --- updated-dependencies: - dependency-name: FluentAssertions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj index 6e4a3abeb..b5c23e55a 100644 --- a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj +++ b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj @@ -11,7 +11,7 @@ - + From eba6e0f1c98d1acbd4441ef7d6af15bcc0cd0cec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 03:44:46 +0000 Subject: [PATCH 13/29] build(deps): bump Microsoft.AspNetCore.OpenApi from 8.0.10 to 8.0.11 Bumps [Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/aspnetcore) from 8.0.10 to 8.0.11. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.10...v8.0.11) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.OpenApi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Plugins.Solax/Plugins.Solax.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins.Solax/Plugins.Solax.csproj b/Plugins.Solax/Plugins.Solax.csproj index 7b993c2eb..8c0a3702c 100644 --- a/Plugins.Solax/Plugins.Solax.csproj +++ b/Plugins.Solax/Plugins.Solax.csproj @@ -8,7 +8,7 @@ - + From e681984c22f692dae9f4d6cc048e77194d92a6af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 03:45:18 +0000 Subject: [PATCH 14/29] build(deps): bump System.Runtime.Caching from 8.0.1 to 9.0.0 Bumps [System.Runtime.Caching](https://github.com/dotnet/runtime) from 8.0.1 to 9.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.1...v9.0.0) --- updated-dependencies: - dependency-name: System.Runtime.Caching dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj index 286cb068d..e86acf8c1 100644 --- a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj +++ b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj @@ -23,7 +23,7 @@ - + From 3c4e6e8f072c8f70a944d1d80c2bb374a904a817 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 03:47:32 +0000 Subject: [PATCH 15/29] build(deps): bump Microsoft.Extensions.DependencyInjection.Abstractions Bumps [Microsoft.Extensions.DependencyInjection.Abstractions](https://github.com/dotnet/runtime) from 8.0.2 to 9.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.2...v9.0.0) --- updated-dependencies: - dependency-name: Microsoft.Extensions.DependencyInjection.Abstractions dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger.Services/TeslaSolarCharger.Services.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger.Services/TeslaSolarCharger.Services.csproj b/TeslaSolarCharger.Services/TeslaSolarCharger.Services.csproj index 2d19ef34f..56ead76c8 100644 --- a/TeslaSolarCharger.Services/TeslaSolarCharger.Services.csproj +++ b/TeslaSolarCharger.Services/TeslaSolarCharger.Services.csproj @@ -9,7 +9,7 @@ - + From 4fe8fd0b6c6d8dd657d999ec75a212c7435ec8bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 03:51:02 +0000 Subject: [PATCH 16/29] build(deps): bump Swashbuckle.AspNetCore from 6.8.1 to 7.0.0 Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.8.1 to 7.0.0. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.8.1...v7.0.0) --- updated-dependencies: - dependency-name: Swashbuckle.AspNetCore dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Plugins.Modbus/Plugins.Modbus.csproj | 2 +- Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj | 2 +- Plugins.SolarEdge/Plugins.SolarEdge.csproj | 2 +- Plugins.Solax/Plugins.Solax.csproj | 2 +- TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Plugins.Modbus/Plugins.Modbus.csproj b/Plugins.Modbus/Plugins.Modbus.csproj index ef3783f2e..c285f03af 100644 --- a/Plugins.Modbus/Plugins.Modbus.csproj +++ b/Plugins.Modbus/Plugins.Modbus.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj index dda5c7e2f..22aed3b3e 100644 --- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj +++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj @@ -11,7 +11,7 @@ - + diff --git a/Plugins.SolarEdge/Plugins.SolarEdge.csproj b/Plugins.SolarEdge/Plugins.SolarEdge.csproj index a9fa7e1ef..85f18ac15 100644 --- a/Plugins.SolarEdge/Plugins.SolarEdge.csproj +++ b/Plugins.SolarEdge/Plugins.SolarEdge.csproj @@ -15,7 +15,7 @@ - + diff --git a/Plugins.Solax/Plugins.Solax.csproj b/Plugins.Solax/Plugins.Solax.csproj index 7b993c2eb..1e5729d85 100644 --- a/Plugins.Solax/Plugins.Solax.csproj +++ b/Plugins.Solax/Plugins.Solax.csproj @@ -14,7 +14,7 @@ - + diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index 20e6cb8a8..fd13b0439 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -66,7 +66,7 @@ - + From a3554f829f58cb8c2bea247aa622e1e6d9adcde6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:04:28 +0000 Subject: [PATCH 17/29] build(deps): bump Microsoft.Extensions.Logging.Debug from 8.0.1 to 9.0.0 Bumps [Microsoft.Extensions.Logging.Debug](https://github.com/dotnet/runtime) from 8.0.1 to 9.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.1...v9.0.0) --- updated-dependencies: - dependency-name: Microsoft.Extensions.Logging.Debug dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj index b5c23e55a..19fa72ff9 100644 --- a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj +++ b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj @@ -13,7 +13,7 @@ - + From 53a4af078daff8a2077334381cc3524eb9a2c1d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:04:44 +0000 Subject: [PATCH 18/29] build(deps): bump Microsoft.AspNetCore.Mvc.NewtonsoftJson and Newtonsoft.Json Bumps [Microsoft.AspNetCore.Mvc.NewtonsoftJson](https://github.com/dotnet/aspnetcore) and [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json). These dependencies needed to be updated together. Updates `Microsoft.AspNetCore.Mvc.NewtonsoftJson` from 8.0.10 to 8.0.11 - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.10...v8.0.11) Updates `Newtonsoft.Json` from 13.0.3 to 13.0.3 - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.3...13.0.3) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Newtonsoft.Json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Plugins.Modbus/Plugins.Modbus.csproj | 2 +- TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins.Modbus/Plugins.Modbus.csproj b/Plugins.Modbus/Plugins.Modbus.csproj index ef3783f2e..a690a5385 100644 --- a/Plugins.Modbus/Plugins.Modbus.csproj +++ b/Plugins.Modbus/Plugins.Modbus.csproj @@ -10,7 +10,7 @@ - + diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index 20e6cb8a8..977f829de 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -46,7 +46,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 0aaf3f1def9fab154b8a3cab890cd633fb576f94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:04:55 +0000 Subject: [PATCH 19/29] build(deps): bump Microsoft.AspNetCore.Components.Forms and Microsoft.Extensions.Logging.Abstractions Bumps [Microsoft.AspNetCore.Components.Forms](https://github.com/dotnet/aspnetcore) and [Microsoft.Extensions.Logging.Abstractions](https://github.com/dotnet/runtime). These dependencies needed to be updated together. Updates `Microsoft.AspNetCore.Components.Forms` from 8.0.10 to 8.0.11 - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.10...v8.0.11) Updates `Microsoft.Extensions.Logging.Abstractions` from 8.0.2 to 8.0.2 - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.2...v8.0.2) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.Forms dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index d1764cca6..4901a4563 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -24,7 +24,7 @@ - + From 0c2bdaf1e3559540cfba209968e2d62bc01e2c1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:05:25 +0000 Subject: [PATCH 20/29] build(deps): bump Microsoft.Extensions.Configuration.Abstractions Bumps [Microsoft.Extensions.Configuration.Abstractions](https://github.com/dotnet/runtime) from 8.0.0 to 9.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v9.0.0) --- updated-dependencies: - dependency-name: Microsoft.Extensions.Configuration.Abstractions dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj index 286cb068d..bbbbeb2c9 100644 --- a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj +++ b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj @@ -18,7 +18,7 @@ - + From 801e66737771af8dca4e94736d398dbd2b3d53bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:07:47 +0000 Subject: [PATCH 21/29] build(deps): bump Microsoft.AspNetCore.Components.WebAssembly.Server Bumps [Microsoft.AspNetCore.Components.WebAssembly.Server](https://github.com/dotnet/aspnetcore) from 8.0.10 to 8.0.11. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.10...v8.0.11) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.WebAssembly.Server dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index 977f829de..f0b7223c2 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -45,7 +45,7 @@ - + all From 372a8df3e2c4584b2ac6e1392ab8b0fb9e3edf02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Nov 2024 00:21:12 +0100 Subject: [PATCH 22/29] fix(chore): use ExpandedChanged as is braking change --- TeslaSolarCharger/Client/Components/BackupComponent.razor | 2 +- TeslaSolarCharger/Client/Pages/Index.razor | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/BackupComponent.razor b/TeslaSolarCharger/Client/Components/BackupComponent.razor index 76a21de12..b12cf9692 100644 --- a/TeslaSolarCharger/Client/Components/BackupComponent.razor +++ b/TeslaSolarCharger/Client/Components/BackupComponent.razor @@ -64,7 +64,7 @@ - + diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index d19191423..05e82e597 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -322,7 +322,7 @@ else } - + @if (_newCarDetailStates.Any(c => c.Key == car.CarId)) { From 6bb95f376e14b1fd3f90de13d3696e84a07405b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Nov 2024 00:01:05 +0100 Subject: [PATCH 23/29] feat(FleetTelemetryWebSocketService): set data based on fleet Telemetry --- .../Server/FleetTelemetryWebSocketService.cs | 95 ++++++ .../FleetTelemetryWebSocketService.cs | 270 ++++++++++++++++-- 2 files changed, 349 insertions(+), 16 deletions(-) diff --git a/TeslaSolarCharger.Tests/Services/Server/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger.Tests/Services/Server/FleetTelemetryWebSocketService.cs index ffb13e434..3584ab1df 100644 --- a/TeslaSolarCharger.Tests/Services/Server/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger.Tests/Services/Server/FleetTelemetryWebSocketService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System; using TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations; +using TeslaSolarCharger.Shared.Dtos.Settings; using TeslaSolarCharger.Shared.Enums; using Xunit.Abstractions; using Xunit; @@ -33,4 +34,98 @@ public void CanDeserializeUnKnownEnumValues() Assert.NotNull(result); Assert.Equal(CarValueType.Unknown, result.Type); } + + [Fact] + public void CanSetIntValueFromIntValue() + { + var carValueLog = new TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog + { + IntValue = 10, + }; + var fleetTelemetryWebSocketService = Mock.Create(); + var car = new DtoCar(); + fleetTelemetryWebSocketService.UpdateDtoCarProperty(car, carValueLog, nameof(DtoCar.SoC)); + Assert.Equal(10, car.SoC); + } + + [Fact] + public void CanSetIntValueFromDoubleValue() + { + var carValueLog = new TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog + { + DoubleValue = 10.45848, + }; + var fleetTelemetryWebSocketService = Mock.Create(); + var car = new DtoCar(); + fleetTelemetryWebSocketService.UpdateDtoCarProperty(car, carValueLog, nameof(DtoCar.SoC)); + Assert.Equal(10, car.SoC); + } + + [Fact] + public void CanSetIntValueFromStringValue() + { + var carValueLog = new TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog + { + StringValue = "10.45848", + }; + var fleetTelemetryWebSocketService = Mock.Create(); + var car = new DtoCar(); + fleetTelemetryWebSocketService.UpdateDtoCarProperty(car, carValueLog, nameof(DtoCar.SoC)); + Assert.Equal(10, car.SoC); + } + + [Theory] + [InlineData("true")] + [InlineData("True")] + [InlineData("TRUE")] + public void CanSetBoolValueFromTrueStringValue(string boolValue) + { + var carValueLog = new TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog + { + StringValue = boolValue, + }; + var fleetTelemetryWebSocketService = Mock.Create(); + var car = new DtoCar(); + fleetTelemetryWebSocketService.UpdateDtoCarProperty(car, carValueLog, nameof(DtoCar.PluggedIn)); + Assert.True(car.PluggedIn); + } + + [Fact] + public void CanSetDoubleValueFromDoubleValue() + { + var carValueLog = new TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog + { + DoubleValue = 10.45848, + }; + var fleetTelemetryWebSocketService = Mock.Create(); + var car = new DtoCar(); + fleetTelemetryWebSocketService.UpdateDtoCarProperty(car, carValueLog, nameof(DtoCar.Latitude)); + Assert.Equal(10.45848, car.Latitude); + } + + [Fact] + public void CanSetDoubleValueFromIntValue() + { + var carValueLog = new TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog + { + IntValue = 10, + }; + var fleetTelemetryWebSocketService = Mock.Create(); + var car = new DtoCar(); + fleetTelemetryWebSocketService.UpdateDtoCarProperty(car, carValueLog, nameof(DtoCar.Latitude)); + Assert.Equal(10, car.Latitude); + } + + [Fact] + public void CanSetDoubleValueFromStringValue() + { + var carValueLog = new TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CarValueLog + { + StringValue = "10.45848", + }; + var fleetTelemetryWebSocketService = Mock.Create(); + var car = new DtoCar(); + fleetTelemetryWebSocketService.UpdateDtoCarProperty(car, carValueLog, nameof(DtoCar.Latitude)); + Assert.Equal(10.45848, car.Latitude); + } } diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index a947ed28c..617cb42ec 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using System.Globalization; using System.Net.WebSockets; using System.Text; using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; @@ -8,14 +9,18 @@ using TeslaSolarCharger.Server.Helper; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Contracts; +using TeslaSolarCharger.Shared.Dtos.Contracts; +using TeslaSolarCharger.Shared.Dtos.Settings; using TeslaSolarCharger.Shared.Enums; namespace TeslaSolarCharger.Server.Services; -public class FleetTelemetryWebSocketService(ILogger logger, +public class FleetTelemetryWebSocketService( + ILogger logger, IConfigurationWrapper configurationWrapper, IDateTimeProvider dateTimeProvider, - IServiceProvider serviceProvider) : IFleetTelemetryWebSocketService + IServiceProvider serviceProvider, + ISettings settings) : IFleetTelemetryWebSocketService { private readonly TimeSpan _heartbeatsendTimeout = TimeSpan.FromSeconds(5); @@ -28,11 +33,7 @@ public async Task ReconnectWebSocketsForEnabledCars() var context = scope.ServiceProvider.GetRequiredService(); var cars = await context.Cars .Where(c => c.UseFleetTelemetry && (c.ShouldBeManaged == true)) - .Select(c => new - { - c.Vin, - c.UseFleetTelemetryForLocationData, - }) + .Select(c => new { c.Vin, c.UseFleetTelemetryForLocationData, }) .ToListAsync(); var bytesToSend = Encoding.UTF8.GetBytes("Heartbeat"); foreach (var car in cars) @@ -41,6 +42,7 @@ public async Task ReconnectWebSocketsForEnabledCars() { continue; } + var existingClient = Clients.FirstOrDefault(c => c.Vin == car.Vin); if (existingClient != default) { @@ -58,6 +60,7 @@ await existingClient.WebSocketClient.SendAsync(segment, WebSocketMessageType.Tex existingClient.WebSocketClient.Dispose(); Clients.Remove(existingClient); } + continue; } else @@ -66,6 +69,7 @@ await existingClient.WebSocketClient.SendAsync(segment, WebSocketMessageType.Tex Clients.Remove(existingClient); } } + ConnectToFleetTelemetryApi(car.Vin, car.UseFleetTelemetryForLocationData); } } @@ -78,8 +82,11 @@ public async Task DisconnectWebSocketsByVin(string vin) { if (client.WebSocketClient.State == WebSocketState.Open) { - await client.WebSocketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", new CancellationTokenSource(_heartbeatsendTimeout).Token).ConfigureAwait(false); + await client.WebSocketClient + .CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", new CancellationTokenSource(_heartbeatsendTimeout).Token) + .ConfigureAwait(false); } + client.WebSocketClient.Dispose(); Clients.Remove(client); } @@ -95,12 +102,14 @@ private async Task ConnectToFleetTelemetryApi(string vin, bool useFleetTelemetry .Where(t => t.ExpiresAtUtc > currentDate) .OrderByDescending(t => t.ExpiresAtUtc) .FirstOrDefaultAsync().ConfigureAwait(false); - if(token == default) + if (token == default) { logger.LogError("Can not connect to WebSocket: No token found for car {vin}", vin); return; } - var url = configurationWrapper.FleetTelemetryApiUrl() + $"teslaToken={token.AccessToken}®ion={token.Region}&vin={vin}&forceReconfiguration=false&includeLocation={useFleetTelemetryForLocationData}"; + + var url = configurationWrapper.FleetTelemetryApiUrl() + + $"teslaToken={token.AccessToken}®ion={token.Region}&vin={vin}&forceReconfiguration=false&includeLocation={useFleetTelemetryForLocationData}"; using var client = new ClientWebSocket(); try { @@ -130,7 +139,8 @@ private async Task ConnectToFleetTelemetryApi(string vin, bool useFleetTelemetry Clients.Remove(dtoClient); if (dtoClient.WebSocketClient.State != WebSocketState.Closed && dtoClient.WebSocketClient.State != WebSocketState.Aborted) { - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", new CancellationTokenSource(_heartbeatsendTimeout).Token).ConfigureAwait(false); + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", + new CancellationTokenSource(_heartbeatsendTimeout).Token).ConfigureAwait(false); } } } @@ -160,11 +170,12 @@ private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken { // Decode the received message var jsonMessage = Encoding.UTF8.GetString(buffer, 0, result.Count); - if(jsonMessage == "Heartbeat") + if (jsonMessage == "Heartbeat") { logger.LogTrace("Received heartbeat: {message}", jsonMessage); continue; } + var message = DeserializeFleetTelemetryMessage(jsonMessage); if (message != null) { @@ -177,6 +188,7 @@ private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken { logger.LogDebug("Save location message for car {carId}", carId); } + var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var carValueLog = new CarValueLog @@ -194,6 +206,58 @@ private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken }; context.CarValueLogs.Add(carValueLog); await context.SaveChangesAsync().ConfigureAwait(false); + var settingsCar = settings.Cars.First(c => c.Vin == vin); + string? propertyName = null; + switch (message.Type) + { + case CarValueType.ChargeAmps: + propertyName = nameof(DtoCar.ChargerActualCurrent); + break; + case CarValueType.ChargeCurrentRequest: + propertyName = nameof(DtoCar.ChargerRequestedCurrent); + break; + case CarValueType.IsPluggedIn: + propertyName = nameof(DtoCar.PluggedIn); + break; + case CarValueType.IsCharging: + if (carValueLog.BooleanValue == true && settingsCar.State != CarStateEnum.Charging) + { + logger.LogDebug("Set car state for car {carId} to charging", carId); + settingsCar.State = CarStateEnum.Charging; + } + else if (carValueLog.BooleanValue == false && settingsCar.State == CarStateEnum.Charging) + { + logger.LogDebug("Set car state for car {carId} to online", carId); + settingsCar.State = CarStateEnum.Online; + } + break; + case CarValueType.ChargerPilotCurrent: + propertyName = nameof(DtoCar.ChargerPilotCurrent); + break; + case CarValueType.Longitude: + propertyName = nameof(DtoCar.Longitude); + break; + case CarValueType.Latitude: + propertyName = nameof(DtoCar.Latitude); + break; + case CarValueType.StateOfCharge: + propertyName = nameof(DtoCar.SoC); + break; + case CarValueType.StateOfChargeLimit: + propertyName = nameof(DtoCar.SocLimit); + break; + case CarValueType.ChargerPhases: + propertyName = nameof(DtoCar.SocLimit); + break; + case CarValueType.ChargerVoltage: + propertyName = nameof(DtoCar.ChargerVoltage); + break; + } + + if (propertyName != default) + { + UpdateDtoCarProperty(settingsCar, carValueLog, propertyName); + } } else { @@ -212,12 +276,186 @@ private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken { var settings = new JsonSerializerSettings { - Converters = new List - { - new EnumDefaultConverter(CarValueType.Unknown), - }, + Converters = new List { new EnumDefaultConverter(CarValueType.Unknown), }, }; var message = JsonConvert.DeserializeObject(jsonMessage, settings); return message; } + + internal void UpdateDtoCarProperty(DtoCar car, CarValueLog carValueLog, string propertyName) + { + logger.LogTrace("{method}({carId}, ***secret***, {propertyName})", nameof(UpdateDtoCarProperty), car.Id, propertyName); + // List of relevant property names + var relevantPropertyNames = new List + { + nameof(CarValueLog.DoubleValue), + nameof(CarValueLog.IntValue), + nameof(CarValueLog.StringValue), + nameof(CarValueLog.UnknownValue), + nameof(CarValueLog.BooleanValue), + }; + + // Filter properties to only the relevant ones + var carValueProperties = typeof(CarValueLog) + .GetProperties() + .Where(p => relevantPropertyNames.Contains(p.Name)); + + object valueToConvert = null; + + // Find the first non-null property in CarValueLog among the relevant ones + foreach (var prop in carValueProperties) + { + var value = prop.GetValue(carValueLog); + if (value != null) + { + valueToConvert = value; + break; + } + } + + if (valueToConvert != null) + { + var dtoProperty = typeof(DtoCar).GetProperty(propertyName); + if (dtoProperty != null) + { + var dtoPropertyType = dtoProperty.PropertyType; + + // Handle nullable types + var targetType = Nullable.GetUnderlyingType(dtoPropertyType) ?? dtoPropertyType; + object? convertedValue = null; + + try + { + // Directly handle numeric conversions without converting to string + if (targetType == typeof(int)) + { + if (valueToConvert is int intValue) + { + convertedValue = intValue; + } + else if (valueToConvert is double doubleValue) + { + // Decide how to handle the fractional part + intValue = (int)Math.Round(doubleValue); // Or Math.Floor(doubleValue), Math.Ceiling(doubleValue) + convertedValue = intValue; + } + else if (valueToConvert is string valueString) + { + // Use InvariantCulture when parsing the string + if (int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out intValue)) + { + convertedValue = intValue; + } + else if (double.TryParse(valueString, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out doubleValue)) + { + intValue = (int)Math.Round(doubleValue); + convertedValue = intValue; + } + } + else if (valueToConvert is IConvertible) + { + convertedValue = Convert.ToInt32(valueToConvert); + } + } + else if (targetType == typeof(double)) + { + if (valueToConvert is double doubleValue) + { + convertedValue = doubleValue; + } + else if (valueToConvert is int intValue) + { + convertedValue = (double)intValue; + } + else if (valueToConvert is string valueString) + { + if (double.TryParse(valueString, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out doubleValue)) + { + convertedValue = doubleValue; + } + } + else if (valueToConvert is IConvertible) + { + convertedValue = Convert.ToDouble(valueToConvert, CultureInfo.InvariantCulture); + } + } + else if (targetType == typeof(decimal)) + { + if (valueToConvert is decimal decimalValue) + { + convertedValue = decimalValue; + } + else if (valueToConvert is double doubleValue) + { + convertedValue = (decimal)doubleValue; + } + else if (valueToConvert is int intValue) + { + convertedValue = (decimal)intValue; + } + else if (valueToConvert is string valueString) + { + if (decimal.TryParse(valueString, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out decimalValue)) + { + convertedValue = decimalValue; + } + } + else if (valueToConvert is IConvertible) + { + convertedValue = Convert.ToDecimal(valueToConvert, CultureInfo.InvariantCulture); + } + } + else if (targetType == typeof(bool)) + { + if (valueToConvert is bool boolValue) + { + convertedValue = boolValue; + } + else if (valueToConvert is string valueString) + { + if (bool.TryParse(valueString, out boolValue)) + { + convertedValue = boolValue; + } + } + else if (valueToConvert is IConvertible) + { + convertedValue = Convert.ToBoolean(valueToConvert, CultureInfo.InvariantCulture); + } + } + else if (targetType == typeof(string)) + { + // Use InvariantCulture to ensure consistent formatting + convertedValue = Convert.ToString(valueToConvert, CultureInfo.InvariantCulture); + } + else + { + // For other types, attempt to convert using ChangeType + if (valueToConvert is IConvertible) + { + convertedValue = Convert.ChangeType(valueToConvert, targetType, CultureInfo.InvariantCulture); + } + else if (targetType.IsAssignableFrom(valueToConvert.GetType())) + { + convertedValue = valueToConvert; + } + } + + // Update the property if conversion was successful + if (convertedValue != null) + { + dtoProperty.SetValue(car, convertedValue); + } + else + { + logger.LogInformation("Do not update {propertyName} on car {carId} as converted value is null", propertyName, car.Id); + } + } + catch (Exception ex) + { + logger.LogError(ex, "Error converting {propertyName} on car {carId}", propertyName, car.Id); + } + } + } + } } From 1c83bb67c17f11a4d786cca7e45700401650a109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Nov 2024 00:03:22 +0100 Subject: [PATCH 24/29] feat(Program): do not autoenable using data from TeslaMate --- TeslaSolarCharger/Server/Program.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 1ac661da0..2a3142bdd 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -118,13 +118,6 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger logger var baseConfigurationService = webApplication.Services.GetRequiredService(); var teslaMateContextWrapper = webApplication.Services.GetRequiredService(); var teslaMateContext = teslaMateContextWrapper.GetTeslaMateContextIfAvailable(); - //This needs to be done before first base configuration update otherwise all TeslaMate values are removed - if (teslaMateContext != default) - { - baseConfiguration.UseTeslaMateIntegration = true; - baseConfiguration.UseTeslaMateAsDataSource = true; - } - await baseConfigurationService.UpdateBaseConfigurationAsync(baseConfiguration); if (teslaMateContext != default) { try From 5f42c85181562e1ba5e25105dab737578e038fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Nov 2024 10:36:19 +0100 Subject: [PATCH 25/29] feat(TeslaFleetApiSerivce): force fleet api refresh if charging or plugged in state changed --- .../Server/Services/TeslaFleetApiService.cs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 9a3855481..6ad70fcc1 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -267,7 +267,7 @@ public async Task RefreshCarData() foreach (var carId in carIds) { var car = settings.Cars.First(c => c.Id == carId); - if (!IsCarDataRefreshNeeded(car)) + if (!(await IsCarDataRefreshNeeded(car))) { logger.LogDebug("Do not refresh car data for car {carId} to prevent rate limits", car.Id); continue; @@ -477,7 +477,7 @@ public async Task RefreshCarData() } } - private bool IsCarDataRefreshNeeded(DtoCar car) + private async Task IsCarDataRefreshNeeded(DtoCar car) { logger.LogTrace("{method}({vin})", nameof(IsCarDataRefreshNeeded), car.Vin); var latestRefresh = car.VehicleDataCalls.OrderByDescending(c => c).FirstOrDefault(); @@ -527,6 +527,38 @@ private bool IsCarDataRefreshNeeded(DtoCar car) return true; } + var isChargingStates = await teslaSolarChargerContext.CarValueLogs + .Where(c => c.Type == CarValueType.IsCharging && c.Source == CarValueSource.FleetTelemetry && c.CarId == car.Id) + .OrderByDescending(c => c.Timestamp) + .Select(c => c.BooleanValue) + .Take(2) + .ToListAsync(); + + if (isChargingStates.Count == 2) + { + if (isChargingStates[0] != isChargingStates[1]) + { + logger.LogDebug("Send a request as Fleet Telemetry detected a change in charging state."); + return true; + } + } + + var isPluggedInStates = await teslaSolarChargerContext.CarValueLogs + .Where(c => c.Type == CarValueType.IsPluggedIn && c.Source == CarValueSource.FleetTelemetry && c.CarId == car.Id) + .OrderByDescending(c => c.Timestamp) + .Select(c => c.BooleanValue) + .Take(2) + .ToListAsync(); + + if (isPluggedInStates.Count == 2) + { + if (isPluggedInStates[0] != isPluggedInStates[1]) + { + logger.LogDebug("Send a request as Fleet Telemetry detected a change in plugged in state."); + return true; + } + } + var latestChargeStartOrWakeUp = car.WakeUpCalls.Concat(car.ChargeStartCalls).OrderByDescending(c => c).FirstOrDefault(); if (latestChargeStartOrWakeUp == default) { From dc568bdeeb088c0eab03137e633f378a3be2bdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Nov 2024 15:05:29 +0100 Subject: [PATCH 26/29] feat(TeslaFleetApiService): request fleet api data on current change from or to zero --- .../Entities/TeslaSolarCharger/CarValueLog.cs | 14 ++- .../Server/Services/TeslaFleetApiService.cs | 100 +++++++++++++----- 2 files changed, 84 insertions(+), 30 deletions(-) diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/CarValueLog.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/CarValueLog.cs index fa13a7c7b..aeeefb2af 100644 --- a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/CarValueLog.cs +++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/CarValueLog.cs @@ -2,19 +2,23 @@ namespace TeslaSolarCharger.Model.Entities.TeslaSolarCharger; -public class CarValueLog +public class CarValueLog : CarValueLogTimeStampAndValues { public int Id { get; set; } - public DateTime Timestamp { get; set; } public CarValueType Type { get; set; } public CarValueSource Source { get; set; } + + public int CarId { get; set; } + public Car Car { get; set; } +} + +public class CarValueLogTimeStampAndValues +{ + public DateTime Timestamp { get; set; } public double? DoubleValue { get; set; } public int? IntValue { get; set; } public string? StringValue { get; set; } public string? UnknownValue { get; set; } public bool? BooleanValue { get; set; } public bool? InvalidValue { get; set; } - - public int CarId { get; set; } - public Car Car { get; set; } } diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 6ad70fcc1..35a223b86 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -527,36 +527,23 @@ private async Task IsCarDataRefreshNeeded(DtoCar car) return true; } - var isChargingStates = await teslaSolarChargerContext.CarValueLogs - .Where(c => c.Type == CarValueType.IsCharging && c.Source == CarValueSource.FleetTelemetry && c.CarId == car.Id) - .OrderByDescending(c => c.Timestamp) - .Select(c => c.BooleanValue) - .Take(2) - .ToListAsync(); - - if (isChargingStates.Count == 2) + if (await FleetTelemetryValueChanged(car.Id, CarValueType.IsCharging, latestRefresh).ConfigureAwait(false)) { - if (isChargingStates[0] != isChargingStates[1]) - { - logger.LogDebug("Send a request as Fleet Telemetry detected a change in charging state."); - return true; - } + logger.LogDebug("Send a request as Fleet Telemetry detected a change in is charging in state."); + return true; } - var isPluggedInStates = await teslaSolarChargerContext.CarValueLogs - .Where(c => c.Type == CarValueType.IsPluggedIn && c.Source == CarValueSource.FleetTelemetry && c.CarId == car.Id) - .OrderByDescending(c => c.Timestamp) - .Select(c => c.BooleanValue) - .Take(2) - .ToListAsync(); + if (await FleetTelemetryValueChanged(car.Id, CarValueType.IsPluggedIn, latestRefresh).ConfigureAwait(false)) + { + logger.LogDebug("Send a request as Fleet Telemetry detected a change in plugged in state."); + return true; + } - if (isPluggedInStates.Count == 2) + var values = await GetLatestTwoValues(car.Id, CarValueType.ChargeAmps).ConfigureAwait(false); + if (LatestValueChangeAfterLatestFleetApiRefresh(latestRefresh, values) && values.Any(v => v.DoubleValue == 0)) { - if (isPluggedInStates[0] != isPluggedInStates[1]) - { - logger.LogDebug("Send a request as Fleet Telemetry detected a change in plugged in state."); - return true; - } + logger.LogDebug("Send a request as Fleet Telemetry detected at least one 0 value in charging amps."); + return true; } var latestChargeStartOrWakeUp = car.WakeUpCalls.Concat(car.ChargeStartCalls).OrderByDescending(c => c).FirstOrDefault(); @@ -584,6 +571,69 @@ private async Task IsCarDataRefreshNeeded(DtoCar car) return false; } + private async Task FleetTelemetryValueChanged(int carId, CarValueType carValueType, DateTime latestRefresh) + { + var values = await GetLatestTwoValues(carId, carValueType).ConfigureAwait(false); + + if (!LatestValueChangeAfterLatestFleetApiRefresh(latestRefresh, values)) + { + return false; + } + + var currentValue = values[0]; + var previousValue = values[1]; + + var doubleValueChanged = !Nullable.Equals(currentValue.DoubleValue, previousValue.DoubleValue); + var intValueChanged = !Nullable.Equals(currentValue.IntValue, previousValue.IntValue); + var stringValueChanged = currentValue.StringValue != previousValue.StringValue; + var unknownValueChanged = currentValue.UnknownValue != previousValue.UnknownValue; + var booleanValueChanged = !Nullable.Equals(currentValue.BooleanValue, previousValue.BooleanValue); + var invalidValueChanged = !Nullable.Equals(currentValue.InvalidValue, previousValue.InvalidValue); + + return (currentValue.Timestamp > latestRefresh) + && (doubleValueChanged || intValueChanged || stringValueChanged || unknownValueChanged || booleanValueChanged || invalidValueChanged); + } + + private static bool LatestValueChangeAfterLatestFleetApiRefresh(DateTime latestRefresh, List values) + { + //Only one value available + if (values.Count != 2) + { + return false; + } + + //latest value change before latest fleet API refresh + if (values.Count(c => c.Timestamp > latestRefresh) < 1) + { + return false; + } + + return true; + } + + private async Task> GetLatestTwoValues(int carId, CarValueType carValueType) + { + var values = await teslaSolarChargerContext.CarValueLogs + .Where(c => c.Type == carValueType + && c.Source == CarValueSource.FleetTelemetry + && c.CarId == carId) + .OrderByDescending(c => c.Timestamp) + .Select(c => new CarValueLogTimeStampAndValues + { + Timestamp = c.Timestamp, + DoubleValue = c.DoubleValue, + IntValue = c.IntValue, + StringValue = c.StringValue, + UnknownValue = c.UnknownValue, + BooleanValue = c.BooleanValue, + InvalidValue = c.InvalidValue, + }) + .Take(2) + .ToListAsync(); + return values; + } + + private CarStateEnum? DetermineCarState(string teslaCarStateString, string? teslaCarShiftState, string teslaCarSoftwareUpdateState, string chargingState) { logger.LogTrace("{method}({teslaCarStateString}, {teslaCarShiftState}, {teslaCarSoftwareUpdateState}, {chargingState})", nameof(DetermineCarState), teslaCarStateString, teslaCarShiftState, teslaCarSoftwareUpdateState, chargingState); From 1d9d96504da590c8a2b3d0d50b79b1e5fbbbd7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Nov 2024 19:06:02 +0100 Subject: [PATCH 27/29] feat(CarBasicConfiguration): update UseFleetTelemetryForLocationData helpter text --- TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs index 248a23626..ae0744343 100644 --- a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs +++ b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs @@ -53,6 +53,6 @@ public CarBasicConfiguration(int id, string? name) [HelperText("If enabled, some data will be transferred via Fleet Telemetry. This improves the delay in the TSC detection of plugin and out of the car, as well as changes in the charging speed. Note: All data transferred via Fleet Telemetry passes my server. For now, the fleet telemetry fields ModuleTempMin, ModuleTempMax,ChargeAmps, ChargeCurrentRequest, ChargeCurrentRequestMax, DetailedChargeState are requested.")] public bool UseFleetTelemetry { get; set; } - [HelperText("Enabling this results in additionally streaming the field Location over my server. If disabled and TeslaMate is not selected as DataSource TSC takes up to 8 minutes to detect if the car is at home. If you do not mind that your car location data passes my server, do not disable this option.")] + [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.")] public bool UseFleetTelemetryForLocationData { get; set; } = true; } From 6779a9521d8c849331875b5df331343e1ba7eaef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 03:22:19 +0000 Subject: [PATCH 28/29] build(deps): bump Microsoft.AspNetCore.Components.WebAssembly.DevServer Bumps [Microsoft.AspNetCore.Components.WebAssembly.DevServer](https://github.com/dotnet/aspnetcore) from 8.0.10 to 9.0.0. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.10...v9.0.0) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.WebAssembly.DevServer dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index 4901a4563..6bdbad9e9 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -26,7 +26,7 @@ - + From 821ca90d19e3041b0dccc4a6537190b9bc2c0610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 18 Nov 2024 22:20:38 +0100 Subject: [PATCH 29/29] refactor(BaseConfigurationSerivce): remove unused code --- TeslaSolarCharger/Server/Services/BaseConfigurationService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs index 90cf0b624..31b95cd8f 100644 --- a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs @@ -17,7 +17,6 @@ public class BaseConfigurationService( JobManager jobManager, ITeslaMateMqttService teslaMateMqttService, ISettings settings, - IPvValueService pvValueService, IDbConnectionStringHelper dbConnectionStringHelper, IConstants constants) : IBaseConfigurationService