diff --git a/PowerControlHub/ConfigController.cpp b/PowerControlHub/ConfigController.cpp index 8f28719..7c1f755 100644 --- a/PowerControlHub/ConfigController.cpp +++ b/PowerControlHub/ConfigController.cpp @@ -615,7 +615,7 @@ ConfigResult ConfigController::generateAuthKeys() if (_config == nullptr) return ConfigResult::InvalidConfig; - char deviceId[32]; + char deviceId[26]; deviceId[0] = '\0'; if (SystemFunctions::GenerateDefaultPassword(deviceId, sizeof(deviceId)) != BufferSuccess) diff --git a/PowerControlHub/ConfigNetworkHandler.cpp b/PowerControlHub/ConfigNetworkHandler.cpp index 0a5b097..12921c9 100644 --- a/PowerControlHub/ConfigNetworkHandler.cpp +++ b/PowerControlHub/ConfigNetworkHandler.cpp @@ -405,14 +405,14 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method, getParamValueBool(params, paramCount, ConfigAuthGenerateParam, generate); if (generate) - { - result = _configController->generateAuthKeys(); + { + result = _configController->generateAuthKeys(); - if (result == ConfigResult::Success) - { - formatJsonResponse(responseBuffer, bufferSize, true, ""); - } + if (result == ConfigResult::Success) + { + formatJsonResponse(responseBuffer, bufferSize, true, ""); } + } else { const char* apiKeyVal = getParamValue(params, paramCount, ConfigAuthApiKeyParam); @@ -426,7 +426,7 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method, ? "API key too long" : "API key invalid"; formatJsonResponse(responseBuffer, bufferSize, false, msg); - return CommandResult(false); + return CommandResult{false, 0}; } } @@ -435,13 +435,14 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method, if (hmacKeyVal) { result = _configController->setAuthHmacKey(hmacKeyVal); + if (result != ConfigResult::Success) { const char* msg = (result == ConfigResult::TooLong) ? "HMAC key too long" : "HMAC key invalid"; formatJsonResponse(responseBuffer, bufferSize, false, msg); - return CommandResult(false); + return CommandResult{false, 0}; } } @@ -449,10 +450,11 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method, if (getParamValueBool(params, paramCount, ConfigAuthEnabledParam, enabled)) { result = _configController->setAuthEnabled(enabled); + if (result != ConfigResult::Success) { formatJsonResponse(responseBuffer, bufferSize, false, "Cannot enable auth: keys not set"); - return CommandResult(false); + return CommandResult{false, 0}; } } @@ -467,6 +469,7 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method, { // N0 - returns all nextion settings as a JSON-style response in responseBuffer Config* config = ConfigManager::getConfigPtr(); + if (config == nullptr) { result = ConfigResult::InvalidConfig; @@ -474,13 +477,13 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method, else { int len = snprintf(responseBuffer, bufferSize, - "en=%u;hw=%u;rx=%u;tx=%u;baud=%lu;uart=%u", - config->nextion.enabled ? 1u : 0u, - config->nextion.isHardwareSerial ? 1u : 0u, - config->nextion.rxPin, - config->nextion.txPin, - config->nextion.baudRate, - config->nextion.uartNum); + "\"en\":%u,\"hw\":%u,\"rx\":%u,\"tx\":%u,\"baud\":%lu,\"uart\":%u", + config->nextion.enabled ? 1u : 0u, + config->nextion.isHardwareSerial ? 1u : 0u, + config->nextion.rxPin, + config->nextion.txPin, + config->nextion.baudRate, + config->nextion.uartNum); result = (len > 0 && len < static_cast(bufferSize)) ? ConfigResult::Success : ConfigResult::InvalidParameter; } diff --git a/PowerControlHub/SystemFunctions.cpp b/PowerControlHub/SystemFunctions.cpp index f4b952b..56189e5 100644 --- a/PowerControlHub/SystemFunctions.cpp +++ b/PowerControlHub/SystemFunctions.cpp @@ -223,7 +223,7 @@ uint8_t SystemFunctions::GenerateDefaultPassword(char* buffer, size_t bufferSize WiFi.macAddress(mac); #endif - snprintf_P(buffer, bufferSize, PSTR("sfb-%02X%02X%02X"), mac[3], mac[4], mac[5]); + snprintf_P(buffer, bufferSize, PSTR("pch-%02X%02X%02X"), mac[3], mac[4], mac[5]); #else randomSeed(analogRead(A0) + analogRead(A1) + millis()); uint32_t storedID = static_cast(random(PasswordRandomMin, LONG_MAX)); diff --git a/PowerControlHubApp/AppShell.xaml.cs b/PowerControlHubApp/AppShell.xaml.cs index e601296..81ef5f6 100644 --- a/PowerControlHubApp/AppShell.xaml.cs +++ b/PowerControlHubApp/AppShell.xaml.cs @@ -14,6 +14,7 @@ public AppShell() Routing.RegisterRoute(nameof(MqttSettingsPage), typeof(MqttSettingsPage)); Routing.RegisterRoute(nameof(SdCardSettingsPage), typeof(SdCardSettingsPage)); Routing.RegisterRoute(nameof(NetworkSecurityPage), typeof(NetworkSecurityPage)); + Routing.RegisterRoute(nameof(NextionSettingsPage), typeof(NextionSettingsPage)); } } } diff --git a/PowerControlHubApp/Internal/Constants.cs b/PowerControlHubApp/Internal/Constants.cs index 2c1b7b8..f54b97d 100644 --- a/PowerControlHubApp/Internal/Constants.cs +++ b/PowerControlHubApp/Internal/Constants.cs @@ -202,6 +202,46 @@ internal static class Constants public const string RouteConfigSdCardSpiPins = "api/config/C4"; public const string RouteConfigSdCardInitSpeed = "api/config/C31"; public const string RouteConfigSdCardCsPin = "api/config/C32"; + // Nextion display config + public const string RouteConfigNextionGet = "api/config/N0"; + public const string RouteConfigNextionSetFormat = "api/config/N{0}?v={1}"; + public const string RouteNextionSettingsPage = "NextionSettingsPage"; + public const string NextionMsgSaveFailed = "Save failed — device unreachable"; + public const string NextionMsgSaved = "Nextion settings saved"; + public const string NextionMsgRefreshed = "Refreshed"; + public const string NextionUart1 = "UART1"; + public const string NextionUart2 = "UART2"; + public const string NextionRebootTitle = "Reboot Required"; + public const string NextionRebootMessage = "Changes require a device reboot to take effect. Reboot device now? (If the device has no reboot endpoint you will need to power-cycle it manually)"; + public const string NextionRebootButton = "Reboot"; + public const string NextionRebootOk = "OK"; + public const string NextionRebootSaved = "Settings saved. Please power-cycle the device to apply Nextion configuration."; + public const string NextionRebootFailed = "Failed to save settings before reboot."; + public const string NextionRouteN1 = "api/config/N1"; + public const string NextionRouteN2 = "api/config/N2"; + public const string NextionRouteN3 = "api/config/N3"; + public const string NextionRouteN4 = "api/config/N4"; + public const string NextionRouteN5 = "api/config/N5"; + public const string NextionRouteN6 = "api/config/N6"; + // Nextion JSON property names (N0 response) + public const string NextionJsonN1 = "n1"; + public const string NextionJsonN2 = "n2"; + public const string NextionJsonN3 = "n3"; + public const string NextionJsonN4 = "n4"; + public const string NextionJsonN5 = "n5"; + public const string NextionJsonN6 = "n6"; + public const string NextionJsonEnabled = "enabled"; + public const string NextionJsonHardwareSerial = "hardwareserial"; + public const string NextionJsonRxPin = "rxpin"; + public const string NextionJsonTxPin = "txpin"; + public const string NextionJsonBaudRate = "baudrate"; + public const string NextionJsonUartNumber = "uartnumber"; + public const string NextionJsonEn = "en"; + public const string NextionJsonHw = "hw"; + public const string NextionJsonRx = "rx"; + public const string NextionJsonTx = "tx"; + public const string NextionJsonBaud = "baud"; + public const string NextionJsonUart = "uart"; public const string RouteConfigAuth = "api/config/C19"; public const string ConfigAuthEnabledParam = "e"; public const string ConfigAuthApiKeyParam = "k"; diff --git a/PowerControlHubApp/MauiProgram.cs b/PowerControlHubApp/MauiProgram.cs index aceb178..e050d3b 100644 --- a/PowerControlHubApp/MauiProgram.cs +++ b/PowerControlHubApp/MauiProgram.cs @@ -109,6 +109,7 @@ public static MauiApp CreateMauiApp() builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); // Pages builder.Services.AddSingleton(); @@ -124,6 +125,7 @@ public static MauiApp CreateMauiApp() builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); #if DEBUG builder.Logging.AddDebug(); diff --git a/PowerControlHubApp/Models/Json/NextionConfigModel.cs b/PowerControlHubApp/Models/Json/NextionConfigModel.cs new file mode 100644 index 0000000..ee7f3f3 --- /dev/null +++ b/PowerControlHubApp/Models/Json/NextionConfigModel.cs @@ -0,0 +1,11 @@ +namespace PowerControlHubApp.Models.Json; + +public sealed class NextionConfigModel +{ + public bool? Enabled { get; set; } + public bool? HardwareSerial { get; set; } + public int? RxPin { get; set; } + public int? TxPin { get; set; } + public int? BaudRate { get; set; } + public int? UartNumber { get; set; } +} diff --git a/PowerControlHubApp/PowerControlHubApp.csproj b/PowerControlHubApp/PowerControlHubApp.csproj index f48ab97..700fc1c 100644 --- a/PowerControlHubApp/PowerControlHubApp.csproj +++ b/PowerControlHubApp/PowerControlHubApp.csproj @@ -106,6 +106,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile diff --git a/PowerControlHubApp/Services/ConfigConnection.cs b/PowerControlHubApp/Services/ConfigConnection.cs index ccf7b67..ce55e5f 100644 --- a/PowerControlHubApp/Services/ConfigConnection.cs +++ b/PowerControlHubApp/Services/ConfigConnection.cs @@ -721,6 +721,97 @@ public async Task GenerateAuthKeysAsync(CancellationToken ct = default) } } + public async Task GetNextionConfigAsync(CancellationToken ct = default) + { + try + { + string json = await _client.GetStringAsync(RouteConfigNextionGet, ct); + using JsonDocument doc = JsonDocument.Parse(json); + + var model = new NextionConfigModel(); + + if (doc.RootElement.ValueKind == JsonValueKind.Object) + { + foreach (var prop in doc.RootElement.EnumerateObject()) + { + string name = prop.Name.ToLowerInvariant(); + + if (name == NextionJsonN1 || name == NextionJsonEnabled || name == NextionJsonEn) + { + if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt32(out int v)) + model.Enabled = v != 0; + } + else if (name == NextionJsonN2 || name == NextionJsonHardwareSerial || name == NextionJsonHw) + { + if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt32(out int v)) + model.HardwareSerial = v != 0; + } + else if (name == NextionJsonN3 || name == NextionJsonRxPin || name == NextionJsonRx) + { + if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt32(out int v)) + model.RxPin = v; + } + else if (name == NextionJsonN4 || name == NextionJsonTxPin || name == NextionJsonTx) + { + if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt32(out int v)) + model.TxPin = v; + } + else if (name == NextionJsonN5 || name == NextionJsonBaudRate || name == NextionJsonBaud) + { + if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt32(out int v)) + model.BaudRate = v; + } + else if (name == NextionJsonN6 || name == NextionJsonUartNumber || name == NextionJsonUart) + { + if (prop.Value.ValueKind == JsonValueKind.Number && prop.Value.TryGetInt32(out int v)) + model.UartNumber = v; + } + } + } + + if (model.Enabled == null || model.HardwareSerial == null || model.RxPin == null || model.TxPin == null || model.BaudRate == null || model.UartNumber == null) + { + int? n1 = await GetConfigIntAsync(NextionRouteN1, ct); + int? n2 = await GetConfigIntAsync(NextionRouteN2, ct); + int? n3 = await GetConfigIntAsync(NextionRouteN3, ct); + int? n4 = await GetConfigIntAsync(NextionRouteN4, ct); + int? n5 = await GetConfigIntAsync(NextionRouteN5, ct); + int? n6 = await GetConfigIntAsync(NextionRouteN6, ct); + + model.Enabled ??= (n1.HasValue ? n1.Value != 0 : (bool?)null); + model.HardwareSerial ??= (n2.HasValue ? n2.Value != 0 : (bool?)null); + model.RxPin ??= n3; + model.TxPin ??= n4; + model.BaudRate ??= n5; + model.UartNumber ??= n6; + } + + return model; + } + catch + { + return new NextionConfigModel(); + } + } + + public async Task SetNextionEnabledAsync(bool enabled, CancellationToken ct = default) + => await SetConfigValueAsync(NextionRouteN1, enabled ? 1 : 0, ct); + + public async Task SetNextionHardwareSerialAsync(bool hardwareSerial, CancellationToken ct = default) + => await SetConfigValueAsync(NextionRouteN2, hardwareSerial ? 1 : 0, ct); + + public async Task SetNextionRxPinAsync(int pin, CancellationToken ct = default) + => await SetConfigValueAsync(NextionRouteN3, pin, ct); + + public async Task SetNextionTxPinAsync(int pin, CancellationToken ct = default) + => await SetConfigValueAsync(NextionRouteN4, pin, ct); + + public async Task SetNextionBaudRateAsync(int baudRate, CancellationToken ct = default) + => await SetConfigValueAsync(NextionRouteN5, baudRate, ct); + + public async Task SetNextionUartNumberAsync(int uartNumber, CancellationToken ct = default) + => await SetConfigValueAsync(NextionRouteN6, uartNumber, ct); + private async Task SyncAuthHandlerFromDeviceAsync(CancellationToken ct) { try diff --git a/PowerControlHubApp/Services/ConfigPoller.cs b/PowerControlHubApp/Services/ConfigPoller.cs index ebd8fe1..eb5e1ad 100644 --- a/PowerControlHubApp/Services/ConfigPoller.cs +++ b/PowerControlHubApp/Services/ConfigPoller.cs @@ -235,6 +235,14 @@ private async Task RunLoopAsync(CancellationToken stoppingToken) public Task GenerateAuthKeysAsync(CancellationToken ct = default) => _connection.GenerateAuthKeysAsync(ct); + public Task GetNextionConfigAsync(CancellationToken ct = default) => _connection.GetNextionConfigAsync(ct); + public Task SetNextionEnabledAsync(bool enabled, CancellationToken ct = default) => _connection.SetNextionEnabledAsync(enabled, ct); + public Task SetNextionHardwareSerialAsync(bool hardwareSerial, CancellationToken ct = default) => _connection.SetNextionHardwareSerialAsync(hardwareSerial, ct); + public Task SetNextionRxPinAsync(int pin, CancellationToken ct = default) => _connection.SetNextionRxPinAsync(pin, ct); + public Task SetNextionTxPinAsync(int pin, CancellationToken ct = default) => _connection.SetNextionTxPinAsync(pin, ct); + public Task SetNextionBaudRateAsync(int baudRate, CancellationToken ct = default) => _connection.SetNextionBaudRateAsync(baudRate, ct); + public Task SetNextionUartNumberAsync(int uartNumber, CancellationToken ct = default) => _connection.SetNextionUartNumberAsync(uartNumber, ct); + public void Dispose() { if (_cts != null) diff --git a/PowerControlHubApp/Services/IConfigConnection.cs b/PowerControlHubApp/Services/IConfigConnection.cs index d8cde22..cadd038 100644 --- a/PowerControlHubApp/Services/IConfigConnection.cs +++ b/PowerControlHubApp/Services/IConfigConnection.cs @@ -63,5 +63,12 @@ public interface IConfigConnection Task SetAuthApiKeyAsync(string apiKey, CancellationToken ct = default); Task SetAuthHmacKeyAsync(string hmacKey, CancellationToken ct = default); Task GenerateAuthKeysAsync(CancellationToken ct = default); + Task GetNextionConfigAsync(CancellationToken ct = default); + Task SetNextionEnabledAsync(bool enabled, CancellationToken ct = default); + Task SetNextionHardwareSerialAsync(bool hardwareSerial, CancellationToken ct = default); + Task SetNextionRxPinAsync(int pin, CancellationToken ct = default); + Task SetNextionTxPinAsync(int pin, CancellationToken ct = default); + Task SetNextionBaudRateAsync(int baudRate, CancellationToken ct = default); + Task SetNextionUartNumberAsync(int uartNumber, CancellationToken ct = default); } } diff --git a/PowerControlHubApp/Services/PowerHubService.cs b/PowerControlHubApp/Services/PowerHubService.cs index e0e27a3..ef0ceee 100644 --- a/PowerControlHubApp/Services/PowerHubService.cs +++ b/PowerControlHubApp/Services/PowerHubService.cs @@ -353,6 +353,41 @@ public Task GenerateAuthKeysAsync(CancellationToken ct = default) return _configConnection.GenerateAuthKeysAsync(ct); } + public Task GetNextionConfigAsync(CancellationToken ct = default) + { + return _configConnection.GetNextionConfigAsync(ct); + } + + public Task SetNextionEnabledAsync(bool enabled, CancellationToken ct = default) + { + return _configConnection.SetNextionEnabledAsync(enabled, ct); + } + + public Task SetNextionHardwareSerialAsync(bool hardwareSerial, CancellationToken ct = default) + { + return _configConnection.SetNextionHardwareSerialAsync(hardwareSerial, ct); + } + + public Task SetNextionRxPinAsync(int pin, CancellationToken ct = default) + { + return _configConnection.SetNextionRxPinAsync(pin, ct); + } + + public Task SetNextionTxPinAsync(int pin, CancellationToken ct = default) + { + return _configConnection.SetNextionTxPinAsync(pin, ct); + } + + public Task SetNextionBaudRateAsync(int baudRate, CancellationToken ct = default) + { + return _configConnection.SetNextionBaudRateAsync(baudRate, ct); + } + + public Task SetNextionUartNumberAsync(int uartNumber, CancellationToken ct = default) + { + return _configConnection.SetNextionUartNumberAsync(uartNumber, ct); + } + private void OnAuthConfigChanged(AuthConfigChanged msg) { if (_dashboardConnection is DashboardConnection dc) diff --git a/PowerControlHubApp/ViewModels/BaseViewModel.cs b/PowerControlHubApp/ViewModels/BaseViewModel.cs index 2f81a99..bffc28e 100644 --- a/PowerControlHubApp/ViewModels/BaseViewModel.cs +++ b/PowerControlHubApp/ViewModels/BaseViewModel.cs @@ -229,7 +229,7 @@ private async Task AutoRefreshLoopAsync(CancellationToken ct) /// updates system status, then calls for /// view-model-specific processing. Handles error cases consistently. /// - protected async Task ExecuteRefreshAsync(CancellationToken ct) + protected virtual async Task ExecuteRefreshAsync(CancellationToken ct) { if (!_service.IsConfigured) { diff --git a/PowerControlHubApp/ViewModels/NextionSettingsViewModel.cs b/PowerControlHubApp/ViewModels/NextionSettingsViewModel.cs new file mode 100644 index 0000000..9a8d0ee --- /dev/null +++ b/PowerControlHubApp/ViewModels/NextionSettingsViewModel.cs @@ -0,0 +1,296 @@ +using PowerControlHubApp.Models.Json; +using PowerControlHubApp.Services; +using System.Collections.ObjectModel; +using System.Windows.Input; +using static PowerControlHubApp.Internal.Constants; + +namespace PowerControlHubApp.ViewModels; + +public sealed class NextionSettingsViewModel : BaseViewModel +{ + private bool _isEnabled; + private bool _isHardwareSerial; + private int _rxPin = UnconfiguredPin; + private int _txPin = UnconfiguredPin; + private int _baudRate; + private int _uartNumber; + private int _uartSelectedIndex; + private bool _isRefreshing; + private bool _isSaving; + + public NextionSettingsViewModel(PowerHubService service, LogService log) + : base(service, log) + { + RefreshCommand = new Command(async () => await RefreshAsync()); + SaveAllCommand = new Command(async () => await SaveAllAsync()); + RebootCommand = new Command(async () => await RebootAsync()); + UartOptions = new ObservableCollection(new[] { NextionUart1, NextionUart2 }); + } + + public ICommand SaveAllCommand { get; } + + public ICommand RebootCommand { get; } + + public bool IsEnabled + { + get => _isEnabled; + + set + { + _isEnabled = value; + OnPropertyChanged(); + } + } + + public bool IsHardwareSerial + { + get => _isHardwareSerial; + + set + { + _isHardwareSerial = value; + OnPropertyChanged(); + } + } + + public int RxPin + { + get => _rxPin; + + set + { + if (_rxPin == value) + return; + + _rxPin = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(RxPinDisplay)); + } + } + + public string RxPinDisplay + { + get => _rxPin == UnconfiguredPin ? string.Empty : _rxPin.ToString(); + + set + { + if (int.TryParse(value, out int p)) + RxPin = p; + else if (string.IsNullOrWhiteSpace(value)) + RxPin = UnconfiguredPin; + } + } + + public int TxPin + { + get => _txPin; + + set + { + if (_txPin == value) + return; + + _txPin = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(TxPinDisplay)); + } + } + + public string TxPinDisplay + { + get => _txPin == UnconfiguredPin ? string.Empty : _txPin.ToString(); + + set + { + if (int.TryParse(value, out int p)) + TxPin = p; + else if (string.IsNullOrWhiteSpace(value)) + TxPin = UnconfiguredPin; + } + } + + public string BaudRate + { + get => _baudRate == 0 ? string.Empty : _baudRate.ToString(); + + set + { + if (int.TryParse(value, out int v)) + _baudRate = v; + else + _baudRate = 0; + OnPropertyChanged(); + } + } + + public int UartNumber + { + get => _uartNumber; + + set + { + _uartNumber = value; + _uartSelectedIndex = value >= 1 ? value - 1 : -1; + OnPropertyChanged(); + OnPropertyChanged(nameof(UartSelectedIndex)); + } + } + + public ObservableCollection UartOptions { get; } + + public int UartSelectedIndex + { + get => _uartSelectedIndex; + + set + { + _uartSelectedIndex = value; + UartNumber = value >= 0 ? value + 1 : 0; + OnPropertyChanged(); + } + } + + public bool IsRefreshing + { + get => _isRefreshing; + + set + { + _isRefreshing = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsNotRefreshing)); + } + } + + public bool IsNotRefreshing => !_isRefreshing; + + public bool IsSaving + { + get => _isSaving; + + set + { + _isSaving = value; + OnPropertyChanged(); + } + } + + public bool HasStatusMessage => !string.IsNullOrEmpty(StatusMessage); + + public async Task RefreshAsync() + { + if (!Service.IsConfigured || _isRefreshing) + return; + + IsRefreshing = true; + + try + { + var cfg = await Service.GetNextionConfigAsync(); + + MainThread.BeginInvokeOnMainThread(() => + { + IsEnabled = cfg.Enabled ?? false; + IsHardwareSerial = cfg.HardwareSerial ?? false; + RxPin = cfg.RxPin ?? UnconfiguredPin; + TxPin = cfg.TxPin ?? UnconfiguredPin; + _baudRate = cfg.BaudRate ?? 0; + UartNumber = cfg.UartNumber ?? 0; + + BaudRate = _baudRate == 0 ? string.Empty : _baudRate.ToString(); + + IsConnected = true; + StatusMessage = $"{NextionMsgRefreshed} {DateTime.Now:HH:mm:ss}"; + OnPropertyChanged(nameof(HasStatusMessage)); + }); + } + catch + { + MainThread.BeginInvokeOnMainThread(() => + { + IsConnected = false; + StatusMessage = MessageDeviceUnreachable; + OnPropertyChanged(nameof(HasStatusMessage)); + }); + } + finally + { + IsRefreshing = false; + } + } + + public async Task SaveAllAsync() + { + if (!Service.IsConfigured || _isSaving) + return; + + _isSaving = true; + bool anyFailed = false; + + try + { + anyFailed |= !await Service.SetNextionEnabledAsync(IsEnabled); + anyFailed |= !await Service.SetNextionHardwareSerialAsync(IsHardwareSerial); + anyFailed |= !await Service.SetNextionRxPinAsync(RxPin == UnconfiguredPin ? UnconfiguredPin : RxPin); + anyFailed |= !await Service.SetNextionTxPinAsync(TxPin == UnconfiguredPin ? UnconfiguredPin : TxPin); + + if (int.TryParse(BaudRate, out int br) && br > 0) + anyFailed |= !await Service.SetNextionBaudRateAsync(br); + + if (UartNumber > 0) + anyFailed |= !await Service.SetNextionUartNumberAsync(UartNumber); + + await Service.SaveSettingsAsync(); + + MainThread.BeginInvokeOnMainThread(() => + { + if (anyFailed) + StatusMessage = NextionMsgSaveFailed; + else + StatusMessage = NextionMsgSaved; + + OnPropertyChanged(nameof(HasStatusMessage)); + }); + } + catch + { + MainThread.BeginInvokeOnMainThread(() => + { + StatusMessage = NextionMsgSaveFailed; + OnPropertyChanged(nameof(HasStatusMessage)); + }); + } + finally + { + _isSaving = false; + } + } + + public async Task RebootAsync() + { + bool confirmed = await Application.Current.Windows[0].Page.DisplayAlertAsync(NextionRebootTitle, NextionRebootMessage, NextionRebootButton, MsgCancel); + + if (!confirmed) + return; + + try + { + await Service.SaveSettingsAsync(); + await Application.Current.Windows[0].Page.DisplayAlertAsync(NextionRebootButton, NextionRebootSaved, NextionRebootOk); + } + catch + { + await Application.Current.Windows[0].Page.DisplayAlertAsync(NextionRebootButton, NextionRebootFailed, NextionRebootOk); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822", Justification = "Called polymorphically from OnDisappearing")] + public void Cleanup() { } + + protected override async Task ExecuteRefreshAsync(CancellationToken ct) + { + await RefreshAsync(); + } + + protected override void OnDataFetched(IndexModel index) { } +} diff --git a/PowerControlHubApp/ViewModels/SystemViewModel.cs b/PowerControlHubApp/ViewModels/SystemViewModel.cs index d786b52..0972315 100644 --- a/PowerControlHubApp/ViewModels/SystemViewModel.cs +++ b/PowerControlHubApp/ViewModels/SystemViewModel.cs @@ -111,6 +111,8 @@ public bool HasPinRestrictions public ICommand NavigateToNetworkSecurityCommand { get; } + public ICommand NavigateToNextionSettingsCommand { get; } + public SystemViewModel(PowerHubService service, LogService log) : base(service, log) { @@ -121,6 +123,7 @@ public SystemViewModel(PowerHubService service, LogService log) NavigateToMqttSettingsCommand = new Command(async () => await Shell.Current.GoToAsync(RouteMqttSettingsPage)); NavigateToSdCardSettingsCommand = new Command(async () => await Shell.Current.GoToAsync(RouteSdCardSettingsPage)); NavigateToNetworkSecurityCommand = new Command(async () => await Shell.Current.GoToAsync(RouteNetworkSecurityPage)); + NavigateToNextionSettingsCommand = new Command(async () => await Shell.Current.GoToAsync(RouteNextionSettingsPage)); } protected override void OnDataFetched(IndexModel index) diff --git a/PowerControlHubApp/Views/NextionSettingsPage.xaml b/PowerControlHubApp/Views/NextionSettingsPage.xaml new file mode 100644 index 0000000..00d2b02 --- /dev/null +++ b/PowerControlHubApp/Views/NextionSettingsPage.xaml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +