@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?))
+ {
+
+ {
+ { "Error", ShouldBeInErrorState.Value },
+ { "ErrorText", ErrorMessage ?? string.Empty },
+ } :new())">
+
+ }
+ else if (LongIdDropDownOptions != default && typeof(T) == typeof(HashSet))
+ {
+
+ {
+ { "Error", ShouldBeInErrorState.Value },
+ { "ErrorText", ErrorMessage ?? string.Empty },
+ } :new())">
+
+
}
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");