diff --git a/TeslaSolarCharger.Tests/Services/Server/ConfigJsonService.cs b/TeslaSolarCharger.Tests/Services/Server/ConfigJsonService.cs index 026352add..c61d3b4b3 100644 --- a/TeslaSolarCharger.Tests/Services/Server/ConfigJsonService.cs +++ b/TeslaSolarCharger.Tests/Services/Server/ConfigJsonService.cs @@ -43,8 +43,8 @@ public void Sets_correct_default_values_on_new_cars() Assert.Equal(16, car.CarConfiguration.MaximumAmpere); Assert.Equal(2, car.CarConfiguration.MinimumAmpere); Assert.Equal(75, car.CarConfiguration.UsableEnergy); - Assert.Equal(DateTime.MaxValue, car.CarState.ShouldStartChargingSince); - Assert.Equal(DateTime.MaxValue, car.CarState.ShouldStopChargingSince); + Assert.Null(car.CarState.ShouldStartChargingSince); + Assert.Null(car.CarState.ShouldStopChargingSince); } } diff --git a/TeslaSolarCharger.Tests/Services/Server/GridService.cs b/TeslaSolarCharger.Tests/Services/Server/GridService.cs index a916d6d1c..ee1a6f027 100644 --- a/TeslaSolarCharger.Tests/Services/Server/GridService.cs +++ b/TeslaSolarCharger.Tests/Services/Server/GridService.cs @@ -107,4 +107,41 @@ public void Can_Get_Integer_From_Xml_Node_Result(string text) Assert.Equal(384, intValue); } + [Theory] + [InlineData(null, null)] + [InlineData("", null)] + [InlineData(" ", null)] + [InlineData(null, "")] + [InlineData(null, " ")] + public void Decides_Correct_Note_Pattern_Type_None(string jsonPattern, string xmlPattern) + { + var gridService = Mock.Create(); + var nodePatternType = gridService.DecideNotePatternType(jsonPattern, xmlPattern); + + Assert.Equal(NodePatternType.None, nodePatternType); + } + + [Theory] + [InlineData("$.data.overage", null)] + [InlineData("$.data.overage", "")] + [InlineData("$.data.overage", " ")] + public void Decides_Correct_Note_Pattern_Type_Json(string jsonPattern, string xmlPattern) + { + var gridService = Mock.Create(); + var nodePatternType = gridService.DecideNotePatternType(jsonPattern, xmlPattern); + + Assert.Equal(NodePatternType.Json, nodePatternType); + } + + [Theory] + [InlineData(null, "Device/Measurements/Measurement")] + [InlineData("", "Device/Measurements/Measurement")] + [InlineData(" ", "Device/Measurements/Measurement")] + public void Decides_Correct_Note_Pattern_Type_Xml(string jsonPattern, string xmlPattern) + { + var gridService = Mock.Create(); + var nodePatternType = gridService.DecideNotePatternType(jsonPattern, xmlPattern); + + Assert.Equal(NodePatternType.Xml, nodePatternType); + } } \ No newline at end of file diff --git a/TeslaSolarCharger.Tests/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger.Tests/Wrappers/ConfigurationWrapper.cs index 093438bc0..d6c301354 100644 --- a/TeslaSolarCharger.Tests/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger.Tests/Wrappers/ConfigurationWrapper.cs @@ -15,7 +15,7 @@ public ConfigurationWrapper(ITestOutputHelper outputHelper) [Fact] public void Get_Not_Nullable_String() { - var configurationWrapper = Mock.Create(); + var configurationWrapper = Mock.Create(); var existingConfigValue = "TeslaMateApiBaseUrl"; var teslaMateApiBaseUrl = @@ -29,7 +29,7 @@ public void Get_Not_Nullable_String() [InlineData("notExisiting")] public void Throw_Exception_On_Null_String(string notExisitingConfigValue) { - var configurationWrapper = Mock.Create(); + var configurationWrapper = Mock.Create(); Assert.Throws( () => configurationWrapper.GetNotNullableConfigurationValue(notExisitingConfigValue)); } @@ -39,7 +39,7 @@ public void Throw_Exception_On_Null_String(string notExisitingConfigValue) [InlineData("notExisiting")] public void Returns_Null_On_Non_Exisiting_Values(string notExisitingConfigValue) { - var configurationWrapper = Mock.Create(); + var configurationWrapper = Mock.Create(); var value = configurationWrapper.GetNullableConfigurationValue(notExisitingConfigValue); Assert.Null(value); @@ -52,7 +52,7 @@ public void Returns_Null_On_Non_Exisiting_Values(string notExisitingConfigValue) [InlineData("notExisiting")] public void Get_TimeSpan_From_Minutes(string configName) { - var configurationWrapper = Mock.Create(); + var configurationWrapper = Mock.Create(); var timespan = configurationWrapper.GetMinutesConfigurationValueIfGreaterThanMinumum(configName, TimeSpan.FromMinutes(1)); @@ -82,7 +82,7 @@ public void Get_TimeSpan_From_Minutes(string configName) [InlineData("notExisiting")] public void Get_TimeSpan_From_Seconds(string configName) { - var configurationWrapper = Mock.Create(); + var configurationWrapper = Mock.Create(); var minimum = TimeSpan.FromSeconds(1); var timespan = configurationWrapper.GetSecondsConfigurationValueIfGreaterThanMinumum(configName, minimum); @@ -109,21 +109,18 @@ public void Get_TimeSpan_From_Seconds(string configName) [Fact] public void GetConfigurationFileDirectory() { - var configurationWrapper = Mock.Create(); + var configurationWrapper = Mock.Create(); var value = configurationWrapper.ConfigFileDirectory(); - - Assert.Equal("configs", value); + var dirInfo = new DirectoryInfo(value); + Assert.Equal("configs", dirInfo.Name); } [Fact] public void GetCarConfigurationFileFullName() { - var configurationWrapper = Mock.Create(); + var configurationWrapper = Mock.Create(); var value = configurationWrapper.CarConfigFileFullName(); - var pathSeparator = Path.DirectorySeparatorChar; - var linuxPathSeparator = '/'; - var windowsPathSeparator = '\\'; - Assert.True(pathSeparator.Equals(linuxPathSeparator) || pathSeparator.Equals(windowsPathSeparator)); - Assert.Equal($"configs{pathSeparator}carConfig.json", value); + var fileInfo = new FileInfo(value); + Assert.Equal("carConfig.json", fileInfo.Name); } } \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor new file mode 100644 index 000000000..310d37143 --- /dev/null +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -0,0 +1,168 @@ +@page "/BaseConfiguration" +@using TeslaSolarCharger.Shared.Dtos.BaseConfiguration +@using System.Runtime.InteropServices +@inject HttpClient HttpClient +@inject IToastService ToastService + +Base Configuration + +

Base Configuration

+ +@if (_dtoBaseConfiguration == null) +{ +

Loading...

+} +else +{ + + + +
+
+ + + Car Ids separated by '|'. Note: The order of the IDs is the order of power distribution. +
+
+
+ + +
+
+ + + Set values higher than 0 to e.g. charge your home battery first, or lower than minimum adjustable power to charge your car first (e.g. 230V * 1A * 3 phases => -691W). +
+
+ + +
+ Use this if consuming power from the grid is a positive value. +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + You can use the name of the container and the default port even though you changed the external port. +
+
+ + + You have to add a geofence with the same name in TeslaMate +
+
+
+ + + You can use the name of the TeslaMate database container +
+
+ + + You can use the internal port of the TeslaMate database container +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+} + +@code { + private DtoBaseConfiguration? _dtoBaseConfiguration; + + protected override async Task OnInitializedAsync() + { + _dtoBaseConfiguration = await HttpClient.GetFromJsonAsync("/api/BaseConfiguration/GetBaseConfiguration"); + } + private async Task HandleValidSubmit() + { + var result = await HttpClient.PutAsJsonAsync($"api/BaseConfiguration/UpdateBaseConfiguration", _dtoBaseConfiguration); + if (result.IsSuccessStatusCode) + { + ToastService.ShowSuccess("Base Configuration updated"); + } + else + { + ToastService.ShowError("Error updating base configuration"); + } + } +} diff --git a/TeslaSolarCharger/Client/Pages/CarSettings.razor b/TeslaSolarCharger/Client/Pages/CarSettings.razor index 9ba027ba9..6a0443522 100644 --- a/TeslaSolarCharger/Client/Pages/CarSettings.razor +++ b/TeslaSolarCharger/Client/Pages/CarSettings.razor @@ -3,7 +3,7 @@ @inject HttpClient HttpClient @inject IToastService ToastService - +Car Settings

CarSettings

@if (_carBasicConfigurations == null) diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index d2f6d33d6..9224a520a 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -7,7 +7,7 @@ @inject HttpClient HttpClient @inject IToastService ToastService -Index +Tesla Solar Charger @if (_settings == null) { @@ -194,7 +194,7 @@ else - Charge start at + Should start charging since @car.CarState.ShouldStartChargingSince @@ -202,7 +202,7 @@ else - Charge end at + Should stop charging since @car.CarState.ShouldStopChargingSince diff --git a/TeslaSolarCharger/Client/Shared/MainLayout.razor b/TeslaSolarCharger/Client/Shared/MainLayout.razor index c0417e00d..6ee3ed008 100644 --- a/TeslaSolarCharger/Client/Shared/MainLayout.razor +++ b/TeslaSolarCharger/Client/Shared/MainLayout.razor @@ -8,7 +8,7 @@
- About + About
diff --git a/TeslaSolarCharger/Client/Shared/NavMenu.razor b/TeslaSolarCharger/Client/Shared/NavMenu.razor index fe938da56..1e55d2f08 100644 --- a/TeslaSolarCharger/Client/Shared/NavMenu.razor +++ b/TeslaSolarCharger/Client/Shared/NavMenu.razor @@ -1,6 +1,6 @@  diff --git a/TeslaSolarCharger/Server/Contracts/IEnvironmentVariableConverter.cs b/TeslaSolarCharger/Server/Contracts/IEnvironmentVariableConverter.cs new file mode 100644 index 000000000..eb90d8bcb --- /dev/null +++ b/TeslaSolarCharger/Server/Contracts/IEnvironmentVariableConverter.cs @@ -0,0 +1,6 @@ +namespace TeslaSolarCharger.Server.Contracts; + +public interface IEnvironmentVariableConverter +{ + Task ConvertAllValues(); +} \ No newline at end of file diff --git a/TeslaSolarCharger/Server/Contracts/ITeslaService.cs b/TeslaSolarCharger/Server/Contracts/ITeslaService.cs index da3331ff4..f3bd66b14 100644 --- a/TeslaSolarCharger/Server/Contracts/ITeslaService.cs +++ b/TeslaSolarCharger/Server/Contracts/ITeslaService.cs @@ -4,7 +4,7 @@ namespace TeslaSolarCharger.Server.Contracts; public interface ITeslaService { - Task StartCharging(int carId, int startAmp, CarState? carState); + Task StartCharging(int carId, int startAmp, CarStateEnum? carState); Task WakeUpCar(int carId); Task StopCharging(int carId); Task SetAmp(int carId, int amps); diff --git a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs new file mode 100644 index 000000000..48ae52828 --- /dev/null +++ b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using TeslaSolarCharger.Server.Contracts; +using TeslaSolarCharger.Shared.Contracts; +using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; + +namespace TeslaSolarCharger.Server.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + public class BaseConfigurationController : ControllerBase + { + private readonly IConfigurationWrapper _configurationWrapper; + + public BaseConfigurationController(IConfigurationWrapper configurationWrapper) + { + _configurationWrapper = configurationWrapper; + } + + [HttpGet] + public Task GetBaseConfiguration() => _configurationWrapper.GetBaseConfigurationAsync(); + + [HttpPut] + public void UpdateBaseConfiguration([FromBody] DtoBaseConfiguration baseConfiguration) => + _configurationWrapper.UpdateBaseConfigurationAsync(baseConfiguration); + } +} diff --git a/TeslaSolarCharger/Server/Helper/EnvironmentVariableConverter.cs b/TeslaSolarCharger/Server/Helper/EnvironmentVariableConverter.cs new file mode 100644 index 000000000..a71ebdf5a --- /dev/null +++ b/TeslaSolarCharger/Server/Helper/EnvironmentVariableConverter.cs @@ -0,0 +1,65 @@ +using TeslaSolarCharger.Server.Contracts; +using TeslaSolarCharger.Shared.Contracts; +using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; + +namespace TeslaSolarCharger.Server.Helper; + +public class EnvironmentVariableConverter : IEnvironmentVariableConverter +{ + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + private readonly IConfigurationWrapper _configurationWrapper; + + public EnvironmentVariableConverter(ILogger logger, IConfiguration configuration, + IConfigurationWrapper configurationWrapper) + { + _logger = logger; + _configuration = configuration; + _configurationWrapper = configurationWrapper; + } + + public async Task ConvertAllValues() + { + _logger.LogTrace("{method}()", nameof(ConvertAllValues)); + if (await _configurationWrapper.IsBaseConfigurationJsonRelevant()) + { + _logger.LogInformation("Do not convert environment variables to json file as json file has been edited."); + return; + } + var dtoBaseConfiguration = new DtoBaseConfiguration() + { + CurrentPowerToGridUrl = _configuration.GetValue("CurrentPowerToGridUrl"), + CurrentInverterPowerUrl = _configuration.GetValue("CurrentInverterPowerUrl"), + TeslaMateApiBaseUrl = _configuration.GetValue("TeslaMateApiBaseUrl"), + UpdateIntervalSeconds = _configuration.GetValue("UpdateIntervalSeconds"), + PvValueUpdateIntervalSeconds = _configuration.GetValue("PvValueUpdateIntervalSeconds"), + CarPriorities = _configuration.GetValue("CarPriorities"), + GeoFence = _configuration.GetValue("GeoFence"), + MinutesUntilSwitchOn = _configuration.GetValue("MinutesUntilSwitchOn"), + MinutesUntilSwitchOff = _configuration.GetValue("MinutesUntilSwitchOff"), + PowerBuffer = _configuration.GetValue("PowerBuffer"), + CurrentPowerToGridJsonPattern = _configuration.GetValue("CurrentPowerToGridJsonPattern"), + CurrentPowerToGridInvertValue = _configuration.GetValue("CurrentPowerToGridInvertValue") == true, + CurrentInverterPowerJsonPattern = _configuration.GetValue("CurrentInverterPowerJsonPattern"), + TelegramBotKey = _configuration.GetValue("TelegramBotKey"), + TelegramChannelId = _configuration.GetValue("TelegramChannelId"), + TeslaMateDbServer = _configuration.GetValue("TeslaMateDbServer"), + TeslaMateDbPort = _configuration.GetValue("TeslaMateDbPort"), + TeslaMateDbDatabaseName = _configuration.GetValue("TeslaMateDbDatabaseName"), + TeslaMateDbUser = _configuration.GetValue("TeslaMateDbUser"), + TeslaMateDbPassword = _configuration.GetValue("TeslaMateDbPassword"), + MqqtClientId = _configuration.GetValue("MqqtClientId"), + MosquitoServer = _configuration.GetValue("MosquitoServer"), + CurrentPowerToGridXmlPattern = _configuration.GetValue("CurrentPowerToGridXmlPattern"), + CurrentPowerToGridXmlAttributeHeaderName = _configuration.GetValue("CurrentPowerToGridXmlAttributeHeaderName"), + CurrentPowerToGridXmlAttributeHeaderValue = _configuration.GetValue("CurrentPowerToGridXmlAttributeHeaderValue"), + CurrentPowerToGridXmlAttributeValueName = _configuration.GetValue("CurrentPowerToGridXmlAttributeValueName"), + CurrentInverterPowerXmlPattern = _configuration.GetValue("CurrentInverterPowerXmlPattern"), + CurrentInverterPowerXmlAttributeHeaderName = _configuration.GetValue("CurrentInverterPowerAttributeHeaderName"), + CurrentInverterPowerXmlAttributeHeaderValue = _configuration.GetValue("CurrentInverterPowerAttributeHeaderValue"), + CurrentInverterPowerXmlAttributeValueName = _configuration.GetValue("CurrentInverterPowerAttributeValueName"), + }; + + await _configurationWrapper.SaveBaseConfiguration(dtoBaseConfiguration); + } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 8c1b2a450..b41704ea8 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -7,6 +7,7 @@ using TeslaSolarCharger.Model.Contracts; using TeslaSolarCharger.Model.EntityFramework; using TeslaSolarCharger.Server.Contracts; +using TeslaSolarCharger.Server.Helper; using TeslaSolarCharger.Server.Scheduling; using TeslaSolarCharger.Server.Services; using TeslaSolarCharger.Shared.Contracts; @@ -63,6 +64,7 @@ options.EnableDetailedErrors(); }, ServiceLifetime.Transient, ServiceLifetime.Transient) .AddTransient() + .AddTransient() ; builder.Host.UseSerilog((context, configuration) => configuration @@ -82,6 +84,9 @@ var app = builder.Build(); +var environmentVariableConverter = app.Services.GetRequiredService(); +await environmentVariableConverter.ConvertAllValues(); + var telegramService = app.Services.GetRequiredService(); await telegramService.SendMessage("Application starting up"); diff --git a/TeslaSolarCharger/Server/Services/ChargingService.cs b/TeslaSolarCharger/Server/Services/ChargingService.cs index 91b9c7a43..658f2f270 100644 --- a/TeslaSolarCharger/Server/Services/ChargingService.cs +++ b/TeslaSolarCharger/Server/Services/ChargingService.cs @@ -172,12 +172,18 @@ private async Task ChangeCarAmp(Car car, int ampToChange) { _logger.LogTrace("{method}({param1}, {param2})", nameof(ChangeCarAmp), car.CarState.Name, ampToChange); //This might happen if only climate is running or car nearly full which means full power is not needed. - if (ampToChange > 0 && car.CarState.ChargerRequestedCurrent > car.CarState.ChargerActualCurrent) + if (ampToChange > 0 && car.CarState.ChargerRequestedCurrent > car.CarState.ChargerActualCurrent && car.CarState.ChargerActualCurrent > 0) { ampToChange = 0; _logger.LogDebug("Set amp to change to {ampToChange} as car does not use full request.", ampToChange); } var finalAmpsToSet = (car.CarState.ChargerRequestedCurrent ?? 0) + ampToChange; + + if (car.CarState.ChargerActualCurrent == 0) + { + finalAmpsToSet = (int)(car.CarState.ChargerActualCurrent + ampToChange); + } + _logger.LogDebug("Amps to set: {amps}", finalAmpsToSet); var ampChange = 0; var minAmpPerCar = car.CarConfiguration.MinimumAmpere; @@ -273,7 +279,7 @@ private async Task ChangeCarAmp(Car car, int ampToChange) if (earliestSwitchOn <= DateTime.Now) { - _logger.LogDebug("Charging should start"); + _logger.LogDebug("Charging is starting"); var startAmp = finalAmpsToSet > maxAmpPerCar ? maxAmpPerCar : finalAmpsToSet; await _teslaService.StartCharging(car.Id, startAmp, car.CarState.State).ConfigureAwait(false); ampChange += startAmp; @@ -348,35 +354,41 @@ private void UpdateEarliestTimesAfterSwitch(int carId) { _logger.LogTrace("{method}({param1})", nameof(UpdateEarliestTimesAfterSwitch), carId); var car = _settings.Cars.First(c => c.Id == carId); - car.CarState.ShouldStopChargingSince = DateTime.MaxValue; - car.CarState.ShouldStartChargingSince = DateTime.MaxValue; + car.CarState.ShouldStopChargingSince = null; + car.CarState.ShouldStartChargingSince = null; } - private DateTime EarliestSwitchOff(int carId) + private DateTime? EarliestSwitchOff(int carId) { _logger.LogTrace("{method}({param1})", nameof(EarliestSwitchOff), carId); - var timeSpanUntilSwitchOff = _configurationWrapper.TimespanUntilSwitchOff(); var car = _settings.Cars.First(c => c.Id == carId); - if (car.CarState.ShouldStopChargingSince == DateTime.MaxValue) + if (car.CarState.ShouldStopChargingSince == null) { - car.CarState.ShouldStopChargingSince = DateTime.Now + timeSpanUntilSwitchOff; + car.CarState.ShouldStopChargingSince = DateTime.Now; } - var earliestSwitchOff = car.CarState.ShouldStopChargingSince; + var timespanUntilSwitchOff = _configurationWrapper.TimespanUntilSwitchOff(); + var earliestSwitchOff = car.CarState.ShouldStopChargingSince + timespanUntilSwitchOff; + _logger.LogDebug("Should start charging since: {shoudStopChargingSince}", car.CarState.ShouldStopChargingSince); + _logger.LogDebug("Timespan until switch on: {timespanUntilSwitchOff}", timespanUntilSwitchOff); + _logger.LogDebug("Earliest switch off: {earliestSwitchOn}", earliestSwitchOff); return earliestSwitchOff; } - private DateTime EarliestSwitchOn(int carId) + private DateTime? EarliestSwitchOn(int carId) { _logger.LogTrace("{method}({param1})", nameof(EarliestSwitchOn), carId); - var timeSpanUntilSwitchOn = _configurationWrapper.TimeUntilSwitchOn(); var car = _settings.Cars.First(c => c.Id == carId); - if (car.CarState.ShouldStartChargingSince == DateTime.MaxValue) + if (car.CarState.ShouldStartChargingSince == null) { - car.CarState.ShouldStartChargingSince = DateTime.Now + timeSpanUntilSwitchOn; + car.CarState.ShouldStartChargingSince = DateTime.Now; } - var earliestSwitchOn = car.CarState.ShouldStartChargingSince; + var timespanUntilSwitchOn = _configurationWrapper.TimespanUntilSwitchOn(); + var earliestSwitchOn = car.CarState.ShouldStartChargingSince + timespanUntilSwitchOn; + _logger.LogDebug("Should start charging since: {shoudStartChargingSince}", car.CarState.ShouldStartChargingSince); + _logger.LogDebug("Timespan until switch on: {timespanUntilSwitchOn}", timespanUntilSwitchOn); + _logger.LogDebug("Earliest switch on: {earliestSwitchOn}", earliestSwitchOn); return earliestSwitchOn; } } \ No newline at end of file diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index 5a675d7f7..754de348a 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -15,30 +15,22 @@ public class ConfigJsonService : IConfigJsonService { private readonly ILogger _logger; private readonly ISettings _settings; - private readonly IConfigurationWrapper _congConfigurationWrapper; + private readonly IConfigurationWrapper _configurationWrapper; public ConfigJsonService(ILogger logger, ISettings settings, - IConfigurationWrapper congConfigurationWrapper) + IConfigurationWrapper configurationWrapper) { _logger = logger; _settings = settings; - _congConfigurationWrapper = congConfigurationWrapper; + _configurationWrapper = configurationWrapper; } private bool CarConfigurationFileExists() { - var path = GetCarConfigurationFileFullPath(); + var path = _configurationWrapper.CarConfigFileFullName(); return File.Exists(path); } - private string GetCarConfigurationFileFullPath() - { - var configFileLocation = _congConfigurationWrapper.CarConfigFileFullName(); - var path = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory?.FullName; - path = Path.Combine(path ?? throw new InvalidOperationException("Could not get Assembly directory"), configFileLocation); - return path; - } - public async Task> GetCarsFromConfiguration() { var cars = new List(); @@ -55,7 +47,7 @@ public async Task> GetCarsFromConfiguration() } } - var carIds = _congConfigurationWrapper.CarPriorities(); + var carIds = _configurationWrapper.CarPriorities(); RemoveOldCars(cars, carIds); var newCarIds = carIds.Where(i => !cars.Any(c => c.Id == i)).ToList(); @@ -70,8 +62,8 @@ internal List DeserializeCarsFromConfigurationString(string fileContent) var cars = JsonConvert.DeserializeObject>(fileContent) ?? throw new InvalidOperationException("Could not deserialize file content"); foreach (var car in cars) { - car.CarState.ShouldStopChargingSince = DateTime.MaxValue; - car.CarState.ShouldStartChargingSince = DateTime.MaxValue; + car.CarState.ShouldStopChargingSince = null; + car.CarState.ShouldStartChargingSince = null; var minDate = new DateTime(2022, 1, 1); if (car.CarConfiguration.LatestTimeToReachSoC < minDate) @@ -86,7 +78,7 @@ internal List DeserializeCarsFromConfigurationString(string fileContent) private async Task GetCarConfigurationFileContent() { - var fileContent = await File.ReadAllTextAsync(GetCarConfigurationFileFullPath()).ConfigureAwait(false); + var fileContent = await File.ReadAllTextAsync(_configurationWrapper.CarConfigFileFullName()).ConfigureAwait(false); return fileContent; } @@ -110,8 +102,8 @@ internal void AddNewCars(List newCarIds, List cars) }, CarState = { - ShouldStartChargingSince = DateTime.MaxValue, - ShouldStopChargingSince = DateTime.MaxValue, + ShouldStartChargingSince = null, + ShouldStopChargingSince = null, }, }; cars.Add(car); @@ -122,7 +114,7 @@ internal void AddNewCars(List newCarIds, List cars) public async Task UpdateConfigJson() { _logger.LogTrace("{method}()", nameof(UpdateConfigJson)); - var configFileLocation = GetCarConfigurationFileFullPath(); + var configFileLocation = _configurationWrapper.CarConfigFileFullName(); var minDate = new DateTime(2022, 1, 1); if (_settings.Cars.Any(c => c.CarConfiguration.UpdatedSincLastWrite || c.CarConfiguration.LatestTimeToReachSoC < minDate)) { @@ -172,7 +164,7 @@ public async Task AddCarIdsToSettings() car.CarConfiguration.MaximumAmpere = 16; } - if (car.CarConfiguration.MinimumAmpere < 16) + if (car.CarConfiguration.MinimumAmpere < 1) { car.CarConfiguration.MinimumAmpere = 1; } diff --git a/TeslaSolarCharger/Server/Services/GridService.cs b/TeslaSolarCharger/Server/Services/GridService.cs index 2038f23b3..f3f55557a 100644 --- a/TeslaSolarCharger/Server/Services/GridService.cs +++ b/TeslaSolarCharger/Server/Services/GridService.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Xml; using Newtonsoft.Json.Linq; +using Quartz.Util; using TeslaSolarCharger.Server.Contracts; using TeslaSolarCharger.Server.Enums; using TeslaSolarCharger.Shared.Contracts; @@ -23,18 +24,10 @@ public GridService(ILogger logger, ITelegramService telegramService public async Task GetCurrentOverage() { _logger.LogTrace("{method}()", nameof(GetCurrentOverage)); - using var httpClient = new HttpClient(); - var requestUri = _configurationWrapper.CurrentPowerToGridUrl(); - _logger.LogDebug("Using {uri} to get current overage.", requestUri); - var response = await httpClient.GetAsync( - requestUri) - .ConfigureAwait(false); - if (!response.IsSuccessStatusCode) + var response = await GetCurrentOverageHtmlResponse().ConfigureAwait(false); + if (response == null) { - _logger.LogError("Could not get current overage. {statusCode}, {reasonPhrase}", response.StatusCode, response.ReasonPhrase); - await _telegramService.SendMessage( - $"Getting current grid power did result in statuscode {response.StatusCode} with reason {response.ReasonPhrase}"); return null; } @@ -43,30 +36,67 @@ await _telegramService.SendMessage( var pattern = ""; var jsonPattern = _configurationWrapper.CurrentPowerToGridJsonPattern(); var xmlPattern = _configurationWrapper.CurrentPowerToGridXmlPattern(); + var nodePatternType = DecideNotePatternType(jsonPattern, xmlPattern); + + if (nodePatternType == NodePatternType.Json) + { + pattern = jsonPattern; + } + else if (nodePatternType == NodePatternType.Xml) + { + pattern = xmlPattern; + } + + var overage = GetValueFromResult(pattern, result, nodePatternType, true); + if (_configurationWrapper.CurrentPowerToGridInvertValue()) + { + overage = -overage; + } + + return overage; + } + + internal NodePatternType DecideNotePatternType(string? jsonPattern, string? xmlPattern) + { + _logger.LogTrace("{method}({param1}, {param2})", nameof(DecideNotePatternType), jsonPattern, xmlPattern); NodePatternType nodePatternType; - if (jsonPattern != null) + if (!jsonPattern.IsNullOrWhiteSpace()) { nodePatternType = NodePatternType.Json; - pattern = jsonPattern; } - else if (xmlPattern != null) + else if (!xmlPattern.IsNullOrWhiteSpace()) { nodePatternType = NodePatternType.Xml; - pattern = xmlPattern; } else { nodePatternType = NodePatternType.None; } + _logger.LogDebug("Node pattern type is {nodePatternType}", nodePatternType); + return nodePatternType; + } - var overage = GetValueFromResult(pattern, result, nodePatternType, true); - if (_configurationWrapper.CurrentPowerToGridInvertValue()) + private async Task GetCurrentOverageHtmlResponse() + { + using var httpClient = new HttpClient(); + var requestUri = _configurationWrapper.CurrentPowerToGridUrl(); + _logger.LogDebug("Using {uri} to get current overage.", requestUri); + var response = await httpClient.GetAsync( + requestUri) + .ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) { - overage = -overage; + _logger.LogError("Could not get current overage. {statusCode}, {reasonPhrase}", response.StatusCode, + response.ReasonPhrase); + await _telegramService.SendMessage( + $"Getting current grid power did result in statuscode {response.StatusCode} with reason {response.ReasonPhrase}"); + return null; } - return overage; + return response; } + public async Task GetCurrentInverterPower() { _logger.LogTrace("{method}()", nameof(GetCurrentInverterPower)); @@ -92,21 +122,16 @@ await _telegramService.SendMessage( var pattern = ""; var jsonPattern = _configurationWrapper.CurrentInverterPowerJsonPattern(); var xmlPattern = _configurationWrapper.CurrentInverterPowerXmlPattern(); - NodePatternType nodePatternType; - if (jsonPattern != null) + var nodePatternType = DecideNotePatternType(jsonPattern, xmlPattern); + + if (nodePatternType == NodePatternType.Json) { - nodePatternType = NodePatternType.Json; pattern = jsonPattern; } - else if (xmlPattern != null) + else if (nodePatternType == NodePatternType.Xml) { - nodePatternType = NodePatternType.Xml; pattern = xmlPattern; } - else - { - nodePatternType = NodePatternType.None; - } return GetValueFromResult(pattern, result, nodePatternType, false); } diff --git a/TeslaSolarCharger/Server/Services/MqttService.cs b/TeslaSolarCharger/Server/Services/MqttService.cs index 2c0b4539c..5fd55e69e 100644 --- a/TeslaSolarCharger/Server/Services/MqttService.cs +++ b/TeslaSolarCharger/Server/Services/MqttService.cs @@ -3,7 +3,7 @@ using TeslaSolarCharger.Server.Contracts; using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.Dtos.Contracts; -using CarState = TeslaSolarCharger.Shared.Enums.CarState; +using TeslaSolarCharger.Shared.Enums; namespace TeslaSolarCharger.Server.Services; @@ -230,29 +230,29 @@ internal void UpdateCar(TeslaMateValue value) switch (value.Value) { case "asleep": - car.CarState.State = CarState.Asleep; + car.CarState.State = CarStateEnum.Asleep; break; case "offline": - car.CarState.State = CarState.Offline; + car.CarState.State = CarStateEnum.Offline; break; case "online": - car.CarState.State = CarState.Online; + car.CarState.State = CarStateEnum.Online; break; case "charging": - car.CarState.State = CarState.Charging; + car.CarState.State = CarStateEnum.Charging; break; case "suspended": - car.CarState.State = CarState.Suspended; + car.CarState.State = CarStateEnum.Suspended; break; case "driving": - car.CarState.State = CarState.Driving; + car.CarState.State = CarStateEnum.Driving; break; case "updating": - car.CarState.State = CarState.Updating; + car.CarState.State = CarStateEnum.Updating; break; default: _logger.LogWarning("Unknown car state deteckted: {carState}", value.Value); - car.CarState.State = CarState.Unknown; + car.CarState.State = CarStateEnum.Unknown; break; } _logger.LogDebug("New car state detected {car state}", car.CarState.StateString); diff --git a/TeslaSolarCharger/Server/Services/TeslamateApiService.cs b/TeslaSolarCharger/Server/Services/TeslamateApiService.cs index 78ef9c550..a4c43428e 100644 --- a/TeslaSolarCharger/Server/Services/TeslamateApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslamateApiService.cs @@ -3,7 +3,7 @@ using TeslaSolarCharger.Server.Contracts; using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.Dtos.Contracts; -using CarState = TeslaSolarCharger.Shared.Enums.CarState; +using TeslaSolarCharger.Shared.Enums; namespace TeslaSolarCharger.Server.Services; @@ -23,18 +23,18 @@ public TeslamateApiService(ILogger logger, ITelegramService _teslaMateBaseUrl = configurationWrapper.TeslaMateApiBaseUrl(); } - public async Task StartCharging(int carId, int startAmp, CarState? carState) + public async Task StartCharging(int carId, int startAmp, CarStateEnum? carState) { _logger.LogTrace("{method}({param1}, {param2}, {param3})", nameof(StartCharging), carId, startAmp, carState); - if (carState == CarState.Offline || - carState == CarState.Asleep) + if (carState == CarStateEnum.Offline || + carState == CarStateEnum.Asleep) { _logger.LogInformation("Wakeup car before charging"); await WakeUpCar(carId); } - if (carState == CarState.Suspended) + if (carState == CarStateEnum.Suspended) { _logger.LogInformation("Logging is suspended"); await ResumeLogging(carId); diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index 4e6bb2d5d..10587cfc1 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -50,6 +50,7 @@ + diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json index a3f16a438..4b267e3f6 100644 --- a/TeslaSolarCharger/Server/appsettings.json +++ b/TeslaSolarCharger/Server/appsettings.json @@ -29,7 +29,7 @@ "AllowedHosts": "*", "ConfigFileLocation": "configs", "CarConfigFilename": "carConfig.json", - "GlobalConfigFileName": "globalConfig.json", + "BaseConfigFileName": "baseConfig.json", "UpdateIntervalSeconds": 30, "PvValueUpdateIntervalSeconds": 1, "MqqtClientId": "TeslaSolarCharger", diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index e223ca719..545c23ed2 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -1,4 +1,6 @@ -namespace TeslaSolarCharger.Shared.Contracts; +using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; + +namespace TeslaSolarCharger.Shared.Contracts; public interface IConfigurationWrapper { @@ -14,7 +16,7 @@ public interface IConfigurationWrapper string TeslaMateApiBaseUrl(); List CarPriorities(); string GeoFence(); - TimeSpan TimeUntilSwitchOn(); + TimeSpan TimespanUntilSwitchOn(); TimeSpan TimespanUntilSwitchOff(); int PowerBuffer(); string? TelegramBotKey(); @@ -33,4 +35,10 @@ public interface IConfigurationWrapper string TeslaMateDbDatabaseName(); string TeslaMateDbUser(); string TeslaMateDbPassword(); + string BaseConfigFileFullName(); + + Task GetBaseConfigurationAsync(); + Task SaveBaseConfiguration(DtoBaseConfiguration baseConfiguration); + Task IsBaseConfigurationJsonRelevant(); + Task UpdateBaseConfigurationAsync(DtoBaseConfiguration dtoBaseConfiguration); } \ No newline at end of file diff --git a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs new file mode 100644 index 000000000..ee59636a5 --- /dev/null +++ b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace TeslaSolarCharger.Shared.Dtos.BaseConfiguration; + +public class BaseConfigurationBase +{ + [Required] + public string CurrentPowerToGridUrl { get; set; } = "http://192.168.1.50:5007/api/GridPower"; + public string? CurrentInverterPowerUrl { get; set; } + [Required] + public string TeslaMateApiBaseUrl { get; set; } = "http://teslamateapi:8080"; + [Required] + [Range(30, int.MaxValue)] + public int UpdateIntervalSeconds { get; set; } = 30; + [Required] + [Range(1, int.MaxValue)] + public int? PvValueUpdateIntervalSeconds { get; set; } = 1; + [Required] + public string CarPriorities { get; set; } = "1"; + [Required] + public string GeoFence { get; set; } = "Home"; + [Required] + [Range(1, int.MaxValue)] + public int MinutesUntilSwitchOn { get; set; } = 5; + [Required] + [Range(1, int.MaxValue)] + public int MinutesUntilSwitchOff { get; set; } = 5; + [Required] + public int PowerBuffer { get; set; } = 0; + public string? CurrentPowerToGridJsonPattern { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool CurrentPowerToGridInvertValue { get; set; } + public string? CurrentInverterPowerJsonPattern { get; set; } + public string? TelegramBotKey { get; set; } + public string? TelegramChannelId { get; set; } + [Required] + public string TeslaMateDbServer { get; set; } = "database"; + [Required] + public int TeslaMateDbPort { get; set; } = 5432; + [Required] + public string TeslaMateDbDatabaseName { get; set; } = "teslamate"; + [Required] + public string TeslaMateDbUser { get; set; } = "teslamate"; + [Required] + [DataType(DataType.Password)] + public string TeslaMateDbPassword { get; set; } = "secret"; + [Required] + public string MqqtClientId { get; set; } = "TeslaSolarCharger"; + [Required] + public string MosquitoServer { get; set; } = "mosquitto"; + public string? CurrentPowerToGridXmlPattern { get; set; } + public string? CurrentPowerToGridXmlAttributeHeaderName { get; set; } + public string? CurrentPowerToGridXmlAttributeHeaderValue { get; set; } + public string? CurrentPowerToGridXmlAttributeValueName { get; set; } + public string? CurrentInverterPowerXmlPattern { get; set; } + public string? CurrentInverterPowerXmlAttributeHeaderName { get; set; } + public string? CurrentInverterPowerXmlAttributeHeaderValue { get; set; } + public string? CurrentInverterPowerXmlAttributeValueName { get; set; } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationJson.cs b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationJson.cs new file mode 100644 index 000000000..532064fa6 --- /dev/null +++ b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationJson.cs @@ -0,0 +1,6 @@ +namespace TeslaSolarCharger.Shared.Dtos.BaseConfiguration; + +public class BaseConfigurationJson : BaseConfigurationBase +{ + public DateTime? LastEditDateTime { get; set; } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/DtoBaseConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/DtoBaseConfiguration.cs new file mode 100644 index 000000000..6b08a4365 --- /dev/null +++ b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/DtoBaseConfiguration.cs @@ -0,0 +1,6 @@ +namespace TeslaSolarCharger.Shared.Dtos.BaseConfiguration; + +public class DtoBaseConfiguration : BaseConfigurationBase +{ + +} \ No newline at end of file diff --git a/TeslaSolarCharger/Shared/Dtos/Settings/CarState.cs b/TeslaSolarCharger/Shared/Dtos/Settings/CarState.cs index 7e8b03bdf..3d692d7a5 100644 --- a/TeslaSolarCharger/Shared/Dtos/Settings/CarState.cs +++ b/TeslaSolarCharger/Shared/Dtos/Settings/CarState.cs @@ -1,10 +1,12 @@ -namespace TeslaSolarCharger.Shared.Dtos.Settings; +using TeslaSolarCharger.Shared.Enums; + +namespace TeslaSolarCharger.Shared.Dtos.Settings; public class CarState { public string? Name { get; set; } - public DateTime ShouldStartChargingSince { get; set; } - public DateTime ShouldStopChargingSince { get; set; } + public DateTime? ShouldStartChargingSince { get; set; } + public DateTime? ShouldStopChargingSince { get; set; } public int? SoC { get; set; } public int? SocLimit { get; set; } public string? Geofence { get; set; } @@ -27,13 +29,23 @@ public int? ChargingPower { get { - var power = ChargerActualCurrent * ChargerVoltage * ActualPhases; + float? currentToUse; + //Next lines because of wrong actual current on currents below 5A + if (ChargerRequestedCurrent < 5 && ChargerActualCurrent == ChargerRequestedCurrent + 1) + { + currentToUse = (float?)(ChargerActualCurrent + ChargerRequestedCurrent) / 2; + } + else + { + currentToUse = ChargerActualCurrent; + } + var power = (int?)(currentToUse * ChargerVoltage * ActualPhases); return power; } } public string? StateString { get; set; } - public Enums.CarState? State { get; set; } + public CarStateEnum? State { get; set; } public bool? Healthy { get; set; } public bool ReducedChargeSpeedWarning { get; set; } } \ No newline at end of file diff --git a/TeslaSolarCharger/Shared/Enums/CarState.cs b/TeslaSolarCharger/Shared/Enums/CarStateEnum.cs similarity index 86% rename from TeslaSolarCharger/Shared/Enums/CarState.cs rename to TeslaSolarCharger/Shared/Enums/CarStateEnum.cs index 46a8f6628..a91bc8891 100644 --- a/TeslaSolarCharger/Shared/Enums/CarState.cs +++ b/TeslaSolarCharger/Shared/Enums/CarStateEnum.cs @@ -1,6 +1,6 @@ namespace TeslaSolarCharger.Shared.Enums; -public enum CarState +public enum CarStateEnum { Asleep, Offline, diff --git a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj index 987f14931..95783f8df 100644 --- a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj +++ b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj @@ -19,5 +19,6 @@ + diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index 6991751e3..d905a9bf4 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -1,7 +1,11 @@ -using System.Runtime.CompilerServices; +using System.Reflection; +using System.Runtime.Caching; +using System.Runtime.CompilerServices; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using TeslaSolarCharger.Shared.Contracts; +using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; [assembly: InternalsVisibleTo("TeslaSolarCharger.Tests")] namespace TeslaSolarCharger.Shared.Wrappers; @@ -10,6 +14,7 @@ public class ConfigurationWrapper : IConfigurationWrapper { private readonly ILogger _logger; private readonly IConfiguration _configuration; + private readonly string _baseConfigurationMemoryCacheName = "baseConfiguration"; public ConfigurationWrapper(ILogger logger, IConfiguration configuration) { @@ -26,29 +31,39 @@ public string CarConfigFileFullName() return Path.Combine(configFileDirectory, value); } + public string BaseConfigFileFullName() + { + var configFileDirectory = ConfigFileDirectory(); + var environmentVariableName = "BaseConfigFileName"; + var value = GetNotNullableConfigurationValue(environmentVariableName); + _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); + return Path.Combine(configFileDirectory, value); + } + internal string ConfigFileDirectory() { var environmentVariableName = "ConfigFileLocation"; var value = GetNotNullableConfigurationValue(environmentVariableName); _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + var path = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory?.FullName; + path = Path.Combine(path ?? throw new InvalidOperationException("Could not get Assembly directory"), value); + return path; } public TimeSpan ChargingValueJobUpdateIntervall() { - var environmentVariableName = "UpdateIntervallSeconds"; var minimum = TimeSpan.FromSeconds(20); - var value = GetSecondsConfigurationValueIfGreaterThanMinumum(environmentVariableName, minimum); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); + var updateIntervalSeconds = GetBaseConfiguration().UpdateIntervalSeconds; + var value = GetValueIfGreaterThanMinimum(TimeSpan.FromSeconds(updateIntervalSeconds), minimum); return value; } public TimeSpan PvValueJobUpdateIntervall() { - var environmentVariableName = "PvValueUpdateIntervalSeconds"; var maximum = ChargingValueJobUpdateIntervall(); var minimum = TimeSpan.FromSeconds(1); - var value = TimeSpan.FromSeconds(_configuration.GetValue(environmentVariableName)); + var updateIntervalSeconds = GetBaseConfiguration().PvValueUpdateIntervalSeconds; + var value = TimeSpan.FromSeconds(updateIntervalSeconds ?? ChargingValueJobUpdateIntervall().TotalSeconds); if (value > maximum) { @@ -58,235 +73,157 @@ public TimeSpan PvValueJobUpdateIntervall() { value = minimum; } - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); return value; } public string MqqtClientId() { - var environmentVariableName = "MqqtClientId"; - var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().MqqtClientId; } public string MosquitoServer() { - var environmentVariableName = "MosquitoServer"; - var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().MosquitoServer; } public string TeslaMateDbServer() { - var environmentVariableName = "TeslaMateDbServer"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TeslaMateDbServer; } public int TeslaMateDbPort() { - var environmentVariableName = "TeslaMateDbPort"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TeslaMateDbPort; } public string TeslaMateDbDatabaseName() { - var environmentVariableName = "TeslaMateDbDatabaseName"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TeslaMateDbDatabaseName; } public string TeslaMateDbUser() { - var environmentVariableName = "TeslaMateDbUser"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TeslaMateDbUser; } public string TeslaMateDbPassword() { - var environmentVariableName = "TeslaMateDbPassword"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TeslaMateDbPassword; } public string CurrentPowerToGridUrl() { - var environmentVariableName = "CurrentPowerToGridUrl"; - var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentPowerToGridUrl; } public string? CurrentInverterPowerUrl() { - var environmentVariableName = "CurrentInverterPowerUrl"; - var value = GetNullableConfigurationValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentInverterPowerUrl; } public string? CurrentPowerToGridJsonPattern() { - var environmentVariableName = "CurrentPowerToGridJsonPattern"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentPowerToGridJsonPattern; } public string? CurrentPowerToGridXmlPattern() { - var environmentVariableName = "CurrentPowerToGridXmlPattern"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentPowerToGridXmlPattern; } public string? CurrentPowerToGridXmlAttributeHeaderName() { - var environmentVariableName = "CurrentPowerToGridXmlAttributeHeaderName"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentPowerToGridXmlAttributeHeaderName; } public string? CurrentPowerToGridXmlAttributeHeaderValue() { - var environmentVariableName = "CurrentPowerToGridXmlAttributeHeaderValue"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentPowerToGridXmlAttributeHeaderValue; } public string? CurrentPowerToGridXmlAttributeValueName() { - var environmentVariableName = "CurrentPowerToGridXmlAttributeValueName"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentPowerToGridXmlAttributeValueName; } public string? CurrentInverterPowerJsonPattern() { - var environmentVariableName = "CurrentInverterPowerJsonPattern"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentInverterPowerJsonPattern; } public string? CurrentInverterPowerXmlPattern() { - var environmentVariableName = "CurrentInverterPowerXmlPattern"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentInverterPowerXmlPattern; } public string? CurrentInverterPowerXmlAttributeHeaderName() { - var environmentVariableName = "CurrentInverterPowerXmlAttributeHeaderName"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentInverterPowerXmlAttributeHeaderName; } public string? CurrentInverterPowerXmlAttributeHeaderValue() { - var environmentVariableName = "CurrentInverterPowerXmlAttributeHeaderValue"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentInverterPowerXmlAttributeHeaderValue; } public string? CurrentInverterPowerXmlAttributeValueName() { - var environmentVariableName = "CurrentInverterPowerXmlAttributeValueName"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentInverterPowerXmlAttributeValueName; } public bool CurrentPowerToGridInvertValue() { - var environmentVariableName = "CurrentPowerToGridInvertValue"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().CurrentPowerToGridInvertValue; } public string TeslaMateApiBaseUrl() { - var environmentVariableName = "TeslaMateApiBaseUrl"; - var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TeslaMateApiBaseUrl; } public List CarPriorities() { - var environmentVariableName = "CarPriorities"; - var rawValue = GetNotNullableConfigurationValue(environmentVariableName); + var rawValue = GetBaseConfiguration().CarPriorities; var value = rawValue.Split("|").Select(id => Convert.ToInt32(id)).ToList(); - _logger.LogDebug("Config value extracted: [{key}]: {@value}", environmentVariableName, value); return value; } public string GeoFence() { - var environmentVariableName = "GeoFence"; - var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().GeoFence; } - public TimeSpan TimeUntilSwitchOn() + public TimeSpan TimespanUntilSwitchOn() { - var environmentVariableName = "MinutesUntilSwitchOn"; + var rawValue = GetBaseConfiguration().MinutesUntilSwitchOn; + var timeSpan = TimeSpan.FromMinutes(rawValue); var minimum = TimeSpan.FromMinutes(1); - var value = GetMinutesConfigurationValueIfGreaterThanMinumum(environmentVariableName, minimum); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); + var value = GetValueIfGreaterThanMinimum(timeSpan, minimum); return value; } public TimeSpan TimespanUntilSwitchOff() { - var environmentVariableName = "MinutesUntilSwitchOff"; + var rawValue = GetBaseConfiguration().MinutesUntilSwitchOff; + var timeSpan = TimeSpan.FromMinutes(rawValue); var minimum = TimeSpan.FromMinutes(1); - var value = GetMinutesConfigurationValueIfGreaterThanMinumum(environmentVariableName, minimum); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); + var value = GetValueIfGreaterThanMinimum(timeSpan, minimum); return value; } public int PowerBuffer() { - var environmentVariableName = "PowerBuffer"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().PowerBuffer; } public string? TelegramBotKey() { - var environmentVariableName = "TelegramBotKey"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TelegramBotKey; } public string? TelegramChannelId() { - var environmentVariableName = "TelegramChannelId"; - var value = _configuration.GetValue(environmentVariableName); - _logger.LogDebug("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return value; + return GetBaseConfiguration().TelegramChannelId; } internal T GetNotNullableConfigurationValue(string environmentVariableName) @@ -332,4 +269,107 @@ private TimeSpan GetValueIfGreaterThanMinimum(TimeSpan value, TimeSpan minimum) return value; } } + + private DtoBaseConfiguration GetBaseConfiguration() + { + return GetBaseConfigurationAsync().GetAwaiter().GetResult(); + } + + + public async Task GetBaseConfigurationAsync() + { + _logger.LogTrace("{method}()", nameof(GetBaseConfiguration)); + var jsonFileContent = await BaseConfigurationJsonFileContent(); + + var dtoBaseConfiguration = JsonConvert.DeserializeObject(jsonFileContent); + + if (dtoBaseConfiguration == null) + { + throw new ArgumentException($"Could not deserialize {jsonFileContent} to {nameof(DtoBaseConfiguration)}"); + } + + return dtoBaseConfiguration; + } + + private async Task BaseConfigurationJsonFileContent() + { + var cache = MemoryCache.Default; + var jsonFileContent = cache[_baseConfigurationMemoryCacheName] as string; + if (jsonFileContent == null) + { + var filePath = BaseConfigFileFullName(); + var cacheItemPolicy = new CacheItemPolicy(); + var filePathList = new List() + { + filePath, + }; + + cacheItemPolicy.ChangeMonitors.Add(new HostFileChangeMonitor(filePathList)); + + if (File.Exists(filePath)) + { + jsonFileContent = await File.ReadAllTextAsync(filePath).ConfigureAwait(false); + + cache.Set(_baseConfigurationMemoryCacheName, jsonFileContent, cacheItemPolicy); + } + } + + return jsonFileContent; + } + + public async Task SaveBaseConfiguration(DtoBaseConfiguration baseConfiguration) + { + var baseConfigurationBase = (BaseConfigurationBase)baseConfiguration; + var baseConfigurationJson = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(baseConfigurationBase)); + //if (false) + //{ + // baseConfigurationJson.LastEditDateTime = DateTime.UtcNow; + //} + var jsonFileContent = JsonConvert.SerializeObject(baseConfigurationJson); + + var configFileLocation = BaseConfigFileFullName(); + var fileInfo = new FileInfo(configFileLocation); + var configDirectoryFullName = fileInfo.Directory?.FullName; + if (!Directory.Exists(configDirectoryFullName)) + { + _logger.LogDebug("Config directory {directoryname} does not exist.", configDirectoryFullName); + Directory.CreateDirectory(configDirectoryFullName ?? throw new InvalidOperationException()); + } + + await UpdateJsonFile(configFileLocation, jsonFileContent).ConfigureAwait(false); + } + + private async Task UpdateJsonFile(string configFileLocation, string jsonFileContent) + { + await File.WriteAllTextAsync(configFileLocation, jsonFileContent); + var cache = MemoryCache.Default; + cache.Remove(_baseConfigurationMemoryCacheName, CacheEntryRemovedReason.ChangeMonitorChanged); + } + + public async Task IsBaseConfigurationJsonRelevant() + { + var jsonContent = await BaseConfigurationJsonFileContent().ConfigureAwait(false); + if (jsonContent == null) + { + return false; + } + var baseConfigurationJson = JsonConvert.DeserializeObject(jsonContent); + return baseConfigurationJson?.LastEditDateTime != null; + } + + public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration dtoBaseConfiguration) + { + var baseConfigurationBase = (BaseConfigurationBase)dtoBaseConfiguration; + var baseConfigurationJson = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(baseConfigurationBase)); + + if (baseConfigurationJson == null) + { + throw new InvalidOperationException("Could not deserialize dtoBaseConfiguration to baseconfigurationJson"); + } + baseConfigurationJson.LastEditDateTime = DateTime.UtcNow; + + var baseConfigurationJsonString = JsonConvert.SerializeObject(baseConfigurationJson); + + await UpdateJsonFile(BaseConfigFileFullName(), baseConfigurationJsonString).ConfigureAwait(false); + } } \ No newline at end of file