diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj index 0eb3832ff..dda5c7e2f 100644 --- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj +++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj @@ -12,7 +12,7 @@ - + diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index e2a899aa6..918a42ac8 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -2,6 +2,7 @@ @using System.Reflection @using System.ComponentModel.DataAnnotations @using System.ComponentModel +@using Lysando.LabStorageV2.UiHelper.Wrapper.Contracts @using MudExtensions @using TeslaSolarCharger.Shared.Attributes @using TeslaSolarCharger.Shared.Helper.Contracts @@ -9,30 +10,37 @@ @inject IConstants Constants @inject IStringHelper StringHelper -@* ReSharper disable once InconsistentNaming *@ -@inject IJSRuntime JSRuntime +@inject IJavaScriptWrapper JavaScriptWrapper @typeparam T @if (!EqualityComparer.Default.Equals(Value, default(T)) || !IsReadOnly) {
-
+
@if (typeof(T) == typeof(DateTime?)) { - + Margin="InputMargin" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())" /> } else if (DropDownOptions != default && typeof(T) == typeof(int?)) { - + ItemCollection="@DropDownOptions.Keys.Select(k => (int?)k).ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else if (DropDownOptions != default && typeof(T) == typeof(HashSet)) { - + ItemCollection="@DropDownOptions.Keys.ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> + + } + else if (LongIdDropDownOptions != default && typeof(T) == typeof(long?)) + { + + + } + else if (LongIdDropDownOptions != default && typeof(T) == typeof(HashSet)) + { + + + } else if (StringIdDropDownOptions != default && typeof(T) == typeof(string)) { @* Even though compiler says ?? string.Empty is not needed in ToStringFunc, it is needed. *@ - + ItemCollection="@StringIdDropDownOptions.Keys.ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else if (StringIdDropDownOptions != default && typeof(T) == typeof(HashSet)) { //ToDo: For label is missing @* Even though compiler says ?? string.Empty is not needed in ToStringFunc, it is needed. *@ - + ItemCollection="@StringIdDropDownOptions.Keys.ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else if (typeof(T) == typeof(short) - || typeof(T) == typeof(short?) - || typeof(T) == typeof(ushort) - || typeof(T) == typeof(ushort?) - || typeof(T) == typeof(int) - || typeof(T) == typeof(int?) - || typeof(T) == typeof(uint) - || typeof(T) == typeof(uint?) - || typeof(T) == typeof(long) - || typeof(T) == typeof(long?) - || typeof(T) == typeof(ulong) - || typeof(T) == typeof(ulong?) - || typeof(T) == typeof(float) - || typeof(T) == typeof(float?) - || typeof(T) == typeof(double) - || typeof(T) == typeof(double?) - || typeof(T) == typeof(decimal) - || typeof(T) == typeof(decimal?)) + || typeof(T) == typeof(short?) + || typeof(T) == typeof(ushort) + || typeof(T) == typeof(ushort?) + || typeof(T) == typeof(int) + || typeof(T) == typeof(int?) + || typeof(T) == typeof(uint) + || typeof(T) == typeof(uint?) + || typeof(T) == typeof(long) + || typeof(T) == typeof(long?) + || typeof(T) == typeof(ulong) + || typeof(T) == typeof(ulong?) + || typeof(T) == typeof(float) + || typeof(T) == typeof(float?) + || typeof(T) == typeof(double) + || typeof(T) == typeof(double?) + || typeof(T) == typeof(decimal) + || typeof(T) == typeof(decimal?)) { - + Margin="InputMargin" + Immediate="@ImmediateValueUpdate" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(NumericFieldAttributes())" /> } else if (IsNormalText()) { if (IsPassword) { - + Margin="InputMargin" + Immediate="@ImmediateValueUpdate" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())" /> } else { - + Margin="InputMargin" + Immediate="@ImmediateValueUpdate" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())" /> } } else if (typeof(T) == typeof(bool) - || typeof(T) == typeof(bool?)) + || typeof(T) == typeof(bool?)) { - + Dense="InputMargin == Margin.Dense" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else @@ -179,7 +304,7 @@ throw new ArgumentOutOfRangeException(); }
- @if(!string.IsNullOrEmpty(HelperText)) + @if (!string.IsNullOrEmpty(HelperText)) {
@HelperText @@ -188,7 +313,7 @@
@if (!string.IsNullOrEmpty(PostfixButtonStartIcon)) { -
+
Constants.DefaultMargin; private Margin InputMargin => Constants.InputMargin; @@ -230,12 +364,27 @@ } } - private string _inputId = Guid.NewGuid().ToString(); - [Parameter] - public int TextAreaMinimumLines { get; set; } = 1; + private bool? _isReadOnlyParameter; + private bool _isIosDevice; + + private IDictionary NumericFieldAttributes() + { + var attributes = new Dictionary(); + + if (ShouldBeInErrorState.HasValue) + { + attributes["Error"] = ShouldBeInErrorState.Value; + attributes["ErrorText"] = ErrorMessage ?? string.Empty; + } - private int TextAreaLines { get; set; } = 1; + if (_isIosDevice) + { + attributes["InputMode"] = InputMode.text; + } + + return attributes; + } private Expression>? ForDateTime { @@ -261,49 +410,9 @@ private int MultiSelectValue { get; set; } = 0; - private string MultiSelectStringValue { get; set; } = string.Empty; - - private async Task GetVisibleLineBreaksCount() - { - return await JSRuntime.InvokeAsync("countVisibleLineBreaks", _inputId); - } + private long MultiSelectLongValue { get; set; } = 0; - private async Task IsTextCutOff() - { - return await JSRuntime.InvokeAsync("isInputTextCutOff", _inputId); - } - - private async Task SetFocusToCurrentInput() - { - await JSRuntime.InvokeVoidAsync("setFocusToInput", _inputId); - } - - private async Task UpdateLineCount(bool shouldSetFocus) - { - var textFieldReplacedByTextarea = false; - if (IsNormalText() && !IsPassword) - { - if (TextAreaLines < 2) - { - if (!await IsTextCutOff()) - { - return; - } - textFieldReplacedByTextarea = true; - } - var lineCount = await GetVisibleLineBreaksCount(); - TextAreaLines = lineCount > TextAreaMinimumLines ? lineCount : TextAreaMinimumLines; - this.StateHasChanged(); - if (shouldSetFocus && textFieldReplacedByTextarea) - { - await SetFocusToCurrentInput(); - } - if (textFieldReplacedByTextarea) - { - await UpdateLineCount(false); - } - } - } + private string MultiSelectStringValue { get; set; } = string.Empty; private Expression>> ForMultiSelectValues { @@ -328,7 +437,7 @@ } throw new InvalidCastException(); } - set => throw new NotImplementedException($"{nameof(ForMultiSelectValues)} can not be set."); + set => throw new NotImplementedException($"{nameof(ForNullableString)} can not be set."); } private Expression> ForNullableInt @@ -341,12 +450,28 @@ } throw new InvalidCastException(); } - set => throw new NotImplementedException($"{nameof(ForMultiSelectValues)} can not be set."); + set => throw new NotImplementedException($"{nameof(ForNullableInt)} can not be set."); + } + + private Expression> ForNullableLong + { + get + { + if (typeof(T) == typeof(long?) && For != null) + { + return (Expression>)(object)For; + } + throw new InvalidCastException(); + } + set => throw new NotImplementedException($"{nameof(ForNullableLong)} can not be set."); } [Parameter] public Dictionary? DropDownOptions { get; set; } + [Parameter] + public Dictionary? LongIdDropDownOptions { get; set; } + [Parameter] public Dictionary? StringIdDropDownOptions { get; set; } @@ -384,17 +509,40 @@ public bool? IsRequiredParameter { get; set; } [Parameter] - public bool? IsReadOnlyParameter { get; set; } + public bool? IsReadOnlyParameter + { + get => _isReadOnlyParameter; + set + { + if (_isReadOnlyParameter != value && _componentRenderedCounter > 0) + { + _isReadOnlyParameter = value; + OnAfterRender(true); + } + else + { + _isReadOnlyParameter = value; + } + } + } [Parameter] public string? HelperText { get; set; } + [Parameter] + public bool ImmediateValueUpdate { get; set; } + + [Parameter] + public bool Clearable { get; set; } + private string? AdornmentText { get; set; } private bool IsRequired { get; set; } private bool IsDisabled { get; set; } private bool IsReadOnly { get; set; } private Adornment Adornment { get; set; } + private int _componentRenderedCounter = 0; + private IEnumerable SelectedMultiSelectValues { get @@ -411,6 +559,22 @@ set => Value = (T)value; } + private IEnumerable SelectedMultiSelectLongValues + { + get + { + if (Value is HashSet selectedValues) + { + return selectedValues; + } + else + { + throw new NotImplementedException(); + } + } + set => Value = (T)value; + } + private IEnumerable SelectedMultiSelectStringValues { get @@ -481,6 +645,33 @@ } } + private long? NullableLongValue + { + get + { + if (typeof(T) == typeof(long?) && Value != null) + { + return (long?)(object)Value; + } + if (Value == null) + { + return null; + } + throw new NotImplementedException(); + } + set + { + if (value != default) + { + Value = (T)(object)value; + } + else + { + Value = default; + } + } + } + private DateTime? DateValue { get @@ -539,17 +730,25 @@ } } - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override async Task OnInitializedAsync() { - if (firstRender && IsNormalText() && !IsPassword) + try { - await UpdateLineCount(false); + _isIosDevice = await JavaScriptWrapper.IsIosDevice(); + } + catch (Exception) + { + _isIosDevice = false; } } + protected override void OnAfterRender(bool firstRender) + { + _componentRenderedCounter++; + } + protected override void OnParametersSet() { - TextAreaLines = TextAreaMinimumLines; if (For == default) { throw new ArgumentException("Expression body is null"); @@ -587,7 +786,7 @@ var postfixAttribute = propertyInfo.GetCustomAttributes(false).SingleOrDefault(); var prefixAttribute = propertyInfo.GetCustomAttributes(false).SingleOrDefault(); - + if (postfixAttribute != default) { @@ -612,30 +811,55 @@ else { Adornment = Adornment.None; - } + } + StateHasChanged(); } - protected override void OnInitialized() + private string GetIntMultiSelectionText(List selectedValues) { - + if (DisplayMultiSelectValues && selectedValues.Count > 0) + { + if (DropDownOptions != null) + { + try + { + return string.Join("; ", selectedValues.Select(x => DropDownOptions[Convert.ToInt32(x)])); + } + catch (Exception) + { + // ignored + } + } + } + + return GetMultiSelectionTextWithoutValues(selectedValues.Count, DropDownOptions?.Count); } - private string GetMultiSelectionText(List selectedValues) + private string GetLongMultiSelectionText(List selectedValues) { if (DisplayMultiSelectValues && selectedValues.Count > 0) { - if (DropDownOptions != null) + if (LongIdDropDownOptions != null) { try { - return string.Join("; ", selectedValues.Select(x => DropDownOptions[Convert.ToInt32(x)])); + return string.Join("; ", selectedValues.Select(x => LongIdDropDownOptions[Convert.ToInt64(x)])); } catch (Exception) { // ignored } } - else if(StringIdDropDownOptions != null) + } + + return GetMultiSelectionTextWithoutValues(selectedValues.Count, LongIdDropDownOptions?.Count); + } + + private string GetMultiSelectionText(List selectedValues) + { + if (DisplayMultiSelectValues && selectedValues.Count > 0) + { + if (StringIdDropDownOptions != null) { try { @@ -647,7 +871,13 @@ } } } - return $"{selectedValues.Count} item{(selectedValues.Count == 1 ? " has" : "s have")} been selected"; + return GetMultiSelectionTextWithoutValues(selectedValues.Count, StringIdDropDownOptions?.Count); + } + + private string GetMultiSelectionTextWithoutValues(int selectedValues, int? availableOptions) + { + var ofText = availableOptions == null ? string.Empty : $"/{availableOptions}"; + return $"{selectedValues}{ofText} item{(selectedValues == 1 ? " has" : "s have")} been selected"; } private void InvokeOnButtonClicked() diff --git a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor new file mode 100644 index 000000000..12b957940 --- /dev/null +++ b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor @@ -0,0 +1,55 @@ +@using TeslaSolarCharger.Shared.Dtos +@using TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues + +@inject HttpClient HttpClient +@inject ISnackbar Snackbar + + +@if(_displayValue && _pvValues != default) +{ +
+ +
+} + + +@code { + private DtoPvValues? _pvValues; + private bool _displayValue; + + protected override async Task OnInitializedAsync() + { + var result = await HttpClient.GetFromJsonAsync>("api/BaseConfiguration/AllowPowerBufferChangeOnHome").ConfigureAwait(false); + if(result == default) + { + return; + } + _displayValue = result.Value; + if(_displayValue) + { + _pvValues = await HttpClient.GetFromJsonAsync("api/Index/GetPvValues").ConfigureAwait(false); + } + + } + + private async Task UpdatePowerBuffer() + { + if(_pvValues == default) + { + return; + } + var response = await HttpClient.GetAsync($"api/BaseConfiguration/UpdatePowerBuffer?powerBuffer={_pvValues.PowerBuffer ?? 0}").ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + Snackbar.Add("Power Buffer updated", Severity.Success); + } + else + { + Snackbar.Add("Failed to update Power Buffer", Severity.Error); + } + + } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor b/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor index a9d77f22e..678d44645 100644 --- a/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor +++ b/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor @@ -5,7 +5,6 @@ @inject HttpClient HttpClient @inject IConstants Constants -@inject ISnackbar Snackbar @implements IDisposable @if (_pvValues != default) @@ -166,14 +165,6 @@
- if (_pvValues.PowerBuffer != default && _pvValues.PowerBuffer != 0) - { -
- -
- } } @@ -641,18 +632,4 @@ return homePower; } - private async Task UpdatePowerBuffer(int? newValue) - { - var response = await HttpClient.GetAsync($"api/BaseConfiguration/UpdatePowerBuffer?powerBuffer={newValue ?? 0}").ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - Snackbar.Add("Power Buffer updated", Severity.Success); - } - else - { - Snackbar.Add("Failed to update Power Buffer", Severity.Error); - } - - } - } \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Helper/Contracts/IJavaScriptWrapper.cs b/TeslaSolarCharger/Client/Helper/Contracts/IJavaScriptWrapper.cs new file mode 100644 index 000000000..047b7632c --- /dev/null +++ b/TeslaSolarCharger/Client/Helper/Contracts/IJavaScriptWrapper.cs @@ -0,0 +1,9 @@ +namespace Lysando.LabStorageV2.UiHelper.Wrapper.Contracts; + +public interface IJavaScriptWrapper +{ + Task SetFocusToElementById(string elementId); + Task RemoveFocusFromElementById(string elementId); + Task OpenUrlInNewTab(string url); + Task IsIosDevice(); +} \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Helper/JavaScriptWrapper.cs b/TeslaSolarCharger/Client/Helper/JavaScriptWrapper.cs new file mode 100644 index 000000000..2cd6a7539 --- /dev/null +++ b/TeslaSolarCharger/Client/Helper/JavaScriptWrapper.cs @@ -0,0 +1,54 @@ +using Lysando.LabStorageV2.UiHelper.Wrapper.Contracts; +using Microsoft.JSInterop; + +namespace Lysando.LabStorageV2.UiHelper.Wrapper; + +public class JavaScriptWrapper(IJSRuntime jsRuntime) : IJavaScriptWrapper +{ + /// + /// Sets the focus to an element with a specific ID + /// + /// ID to set the focus on + /// Was the ID set successfully + public async Task SetFocusToElementById(string elementId) + { + try + { + return await jsRuntime.InvokeAsync("setFocus", elementId); + } + catch (Exception) + { + return false; + } + } + + public async Task RemoveFocusFromElementById(string elementId) + { + try + { + return await jsRuntime.InvokeAsync("removeFocus", elementId); + } + catch (Exception) + { + return false; + } + } + + public async Task OpenUrlInNewTab(string url) + { + await jsRuntime.InvokeVoidAsync("openInNewTab", url); + } + + public async Task IsIosDevice() + { + try + { + var device = await jsRuntime.InvokeAsync("detectDevice"); + return device == "iOS"; + } + catch (Exception) + { + return false; + } + } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index 858bb319c..8e19fbd9c 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -1,4 +1,4 @@ -@page "/BaseConfiguration" +@page "/BaseConfiguration" @using System.Globalization @using TeslaSolarCharger.Client.Helper.Contracts @using TeslaSolarCharger.Shared.Dtos.BaseConfiguration @@ -153,11 +153,13 @@ else + HelpText="Set values higher than 0 to always have some overage (power to grid). Set values lower than 0 to always consume some power from the grid."> + + - +
diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index b2028c45a..d19191423 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -42,6 +42,7 @@ + @if (_carBaseStates == null || _carBaseSettings == null) diff --git a/TeslaSolarCharger/Client/Program.cs b/TeslaSolarCharger/Client/Program.cs index 8e8d173d0..6c2f2bbcb 100644 --- a/TeslaSolarCharger/Client/Program.cs +++ b/TeslaSolarCharger/Client/Program.cs @@ -1,3 +1,5 @@ +using Lysando.LabStorageV2.UiHelper.Wrapper; +using Lysando.LabStorageV2.UiHelper.Wrapper.Contracts; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor; @@ -21,6 +23,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSharedDependencies(); diff --git a/TeslaSolarCharger/Client/wwwroot/index.html b/TeslaSolarCharger/Client/wwwroot/index.html index edb707476..3301a3b53 100644 --- a/TeslaSolarCharger/Client/wwwroot/index.html +++ b/TeslaSolarCharger/Client/wwwroot/index.html @@ -37,6 +37,7 @@ + diff --git a/TeslaSolarCharger/Client/wwwroot/js/javaScriptWrapperFunctions.js b/TeslaSolarCharger/Client/wwwroot/js/javaScriptWrapperFunctions.js new file mode 100644 index 000000000..4843b48f8 --- /dev/null +++ b/TeslaSolarCharger/Client/wwwroot/js/javaScriptWrapperFunctions.js @@ -0,0 +1,30 @@ +function setFocus(elementId) { + const element = document.getElementById(elementId); + if (element) { + element.focus(); + return true; + } + return false; +} + +function removeFocus(elementId) { + const element = document.getElementById(elementId); + if (element) { + element.blur(); + return true; + } + return false; +} + +function openInNewTab(url) { + window.open(url, '_blank'); +} + +function detectDevice() { + var ua = navigator.userAgent || navigator.vendor || window.opera; + // iOS detection + if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) { + return "iOS"; + } + return "Other"; +} diff --git a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs index 961809373..119d579ee 100644 --- a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs +++ b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs @@ -15,6 +15,9 @@ public class BaseConfigurationController( [HttpGet] public Task GetBaseConfiguration() => configurationWrapper.GetBaseConfigurationAsync(); + [HttpGet] + public DtoValue AllowPowerBufferChangeOnHome() => new(configurationWrapper.AllowPowerBufferChangeOnHome()); + [HttpPut] public Task UpdateBaseConfiguration([FromBody] DtoBaseConfiguration baseConfiguration) => service.UpdateBaseConfigurationAsync(baseConfiguration); diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index dedf2ea3b..20e6cb8a8 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -60,7 +60,7 @@ - + diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index eb8bd93ac..21aa9eda2 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -107,4 +107,5 @@ public interface IConfigurationWrapper TimeSpan BleUsageStopAfterError(); bool UseTeslaMateIntegration(); string FleetTelemetryApiUrl(); + bool AllowPowerBufferChangeOnHome(); } diff --git a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs index 51ede9127..b54c76ea4 100644 --- a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs +++ b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs @@ -45,6 +45,8 @@ 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.")] + public bool AllowPowerBufferChangeOnHome { get; set; } public string? CurrentPowerToGridJsonPattern { get; set; } public decimal CurrentPowerToGridCorrectionFactor { get; set; } = 1; public string? CurrentInverterPowerJsonPattern { get; set; } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index 059f57ab2..fd85b529b 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -212,6 +212,11 @@ public string FleetTelemetryApiUrl() return value; } + public bool AllowPowerBufferChangeOnHome() + { + return GetBaseConfiguration().AllowPowerBufferChangeOnHome; + } + public bool IsDevelopmentEnvironment() { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");