Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions PowerControlHub/ConfigController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,27 @@ ConfigResult ConfigController::setRtcPins(const uint8_t dataPin, const uint8_t c
if (_config == nullptr)
return ConfigResult::InvalidConfig;

if (PinGuard::isBlocked(PinGuard::validate(dataPin, PinUse::Output)) ||
PinGuard::isBlocked(PinGuard::validate(clockPin, PinUse::Output)) ||
PinGuard::isBlocked(PinGuard::validate(resetPin, PinUse::Output)))
// Temporarily clear current RTC pin assignments so they don't
// conflict with themselves during validation (getUsedPins collects
// the current rtc.dataPin/clockPin/resetPin values).
const uint8_t oldData = _config->rtc.dataPin;
const uint8_t oldClock = _config->rtc.clockPin;
const uint8_t oldReset = _config->rtc.resetPin;

_config->rtc.dataPin = PinDisabled;
_config->rtc.clockPin = PinDisabled;
_config->rtc.resetPin = PinDisabled;

const bool blocked = PinGuard::isBlocked(PinGuard::validate(dataPin, PinUse::Output))
|| PinGuard::isBlocked(PinGuard::validate(clockPin, PinUse::Output))
|| PinGuard::isBlocked(PinGuard::validate(resetPin, PinUse::Output));

if (blocked)
{
// Restore old values on failure
_config->rtc.dataPin = oldData;
_config->rtc.clockPin = oldClock;
_config->rtc.resetPin = oldReset;
return ConfigResult::InvalidPin;
}

Expand Down
10 changes: 10 additions & 0 deletions PowerControlHub/ConfigNetworkHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,16 @@ void ConfigNetworkHandler::formatStatusJson(IWifiClient* client)
client->print(config->sdCard.csPin);
client->print(",");

// C18 RTC DS1302 pins
client->print("\"rtcPins\":{");
client->print("\"dat\":");
client->print(config->rtc.dataPin);
client->print(",\"clk\":");
client->print(config->rtc.clockPin);
client->print(",\"rst\":");
client->print(config->rtc.resetPin);
client->print("},");

// C19 Network auth
client->print("\"auth\":{");
client->print("\"enabled\":");
Expand Down
1 change: 1 addition & 0 deletions PowerControlHubApp/AppShell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public AppShell()
Routing.RegisterRoute(nameof(TimeSettingsPage), typeof(TimeSettingsPage));
Routing.RegisterRoute(nameof(MqttSettingsPage), typeof(MqttSettingsPage));
Routing.RegisterRoute(nameof(SdCardSettingsPage), typeof(SdCardSettingsPage));
Routing.RegisterRoute(nameof(RtcSettingsPage), typeof(RtcSettingsPage));
Routing.RegisterRoute(nameof(NetworkSecurityPage), typeof(NetworkSecurityPage));
Routing.RegisterRoute(nameof(NextionSettingsPage), typeof(NextionSettingsPage));
}
Expand Down
10 changes: 10 additions & 0 deletions PowerControlHubApp/Internal/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ 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";
// RTC config
public const string RouteConfigRtc = "api/config/C18";
public const string RouteRtcSettingsPage = "RtcSettingsPage";
public const string RtcMsgSaveFailed = "Save failed — device unreachable";
public const string RtcMsgSaved = "RTC settings saved";
public const string RtcMsgRefreshed = "Refreshed";
public const string JsonRtcDataPin = "dat";
public const string JsonRtcClockPin = "clk";
public const string JsonRtcResetPin = "rst";
public const int RtcPinDisabled = 255;
// Nextion display config
public const string RouteConfigNextionGet = "api/config/N0";
public const string RouteConfigNextionSetFormat = "api/config/N{0}?v={1}";
Expand Down
2 changes: 2 additions & 0 deletions PowerControlHubApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public static MauiApp CreateMauiApp()
builder.Services.AddTransient<TimeSettingsViewModel>();
builder.Services.AddTransient<MqttSettingsViewModel>();
builder.Services.AddTransient<SdCardSettingsViewModel>();
builder.Services.AddTransient<RtcSettingsViewModel>();
builder.Services.AddTransient<NetworkSecurityViewModel>();
builder.Services.AddTransient<NextionSettingsViewModel>();

Expand All @@ -124,6 +125,7 @@ public static MauiApp CreateMauiApp()
builder.Services.AddTransient<TimeSettingsPage>();
builder.Services.AddTransient<MqttSettingsPage>();
builder.Services.AddTransient<SdCardSettingsPage>();
builder.Services.AddTransient<RtcSettingsPage>();
builder.Services.AddTransient<NetworkSecurityPage>();
builder.Services.AddTransient<NextionSettingsPage>();

Expand Down
3 changes: 3 additions & 0 deletions PowerControlHubApp/Models/Json/ConfigModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,8 @@ public sealed class ConfigModel

[JsonPropertyName("sdCardCsPin")]
public int SdCardCsPin { get; set; }

[JsonPropertyName("rtcPins")]
public RtcConfigModel Rtc { get; set; }
}
}
16 changes: 16 additions & 0 deletions PowerControlHubApp/Models/RtcConfigModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;

namespace PowerControlHubApp.Models
{
public sealed class RtcConfigModel
{
[JsonPropertyName("dat")]
public int Dat { get; set; }

[JsonPropertyName("clk")]
public int Clk { get; set; }

[JsonPropertyName("rst")]
public int Rst { get; set; }
}
}
3 changes: 3 additions & 0 deletions PowerControlHubApp/PowerControlHubApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
<MauiXaml Update="Views\RelayDetailPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\RtcSettingsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\SdCardSettingsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
Expand Down
49 changes: 49 additions & 0 deletions PowerControlHubApp/Services/ConfigConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,55 @@ public async Task<bool> SetSdCardCsPinAsync(int pin, CancellationToken ct = defa
return await SetConfigValueAsync(RouteConfigSdCardCsPin, pin, ct);
}

public async Task<(int DataPin, int ClockPin, int ResetPin)?> GetRtcPinsAsync(CancellationToken ct = default)
{
try
{
string json = await _client.GetStringAsync(RouteConfigRtc, ct);
using JsonDocument doc = JsonDocument.Parse(json);

if (doc.RootElement.TryGetProperty(ResultSuccess, out var success) &&
success.GetBoolean())
{
int? data = null;
int? clock = null;
int? reset = null;

if (doc.RootElement.TryGetProperty(JsonRtcDataPin, out var d) && d.ValueKind == JsonValueKind.Number && d.TryGetInt32(out int dv))
data = dv;

if (doc.RootElement.TryGetProperty(JsonRtcClockPin, out var c) && c.ValueKind == JsonValueKind.Number && c.TryGetInt32(out int cv))
clock = cv;

if (doc.RootElement.TryGetProperty(JsonRtcResetPin, out var r) && r.ValueKind == JsonValueKind.Number && r.TryGetInt32(out int rv))
reset = rv;

if (data.HasValue && clock.HasValue && reset.HasValue)
return (data.Value, clock.Value, reset.Value);
}
}
catch
{
// fall through
}

return null;
}

public async Task<bool> SetRtcPinsAsync(int dataPin, int clockPin, int resetPin, CancellationToken ct = default)
{
try
{
string url = $"{RouteConfigRtc}?{JsonRtcDataPin}={dataPin}&{JsonRtcClockPin}={clockPin}&{JsonRtcResetPin}={resetPin}";
HttpResponseMessage response = await _client.PostAsync(url, null, ct);
return response.IsSuccessStatusCode;
}
catch
{
return false;
}
}

public async Task<AuthConfigModel> GetAuthConfigAsync(CancellationToken ct = default)
{
try
Expand Down
4 changes: 4 additions & 0 deletions PowerControlHubApp/Services/ConfigPoller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ private async Task RunLoopAsync(CancellationToken stoppingToken)

public Task<bool> SetSdCardCsPinAsync(int pin, CancellationToken ct = default) => _connection.SetSdCardCsPinAsync(pin, ct);

public Task<(int DataPin, int ClockPin, int ResetPin)?> GetRtcPinsAsync(CancellationToken ct = default) => _connection.GetRtcPinsAsync(ct);

public Task<bool> SetRtcPinsAsync(int dataPin, int clockPin, int resetPin, CancellationToken ct = default) => _connection.SetRtcPinsAsync(dataPin, clockPin, resetPin, ct);

public Task<AuthConfigModel> GetAuthConfigAsync(CancellationToken ct = default) => _connection.GetAuthConfigAsync(ct);

public Task<bool> SetAuthEnabledAsync(bool enabled, CancellationToken ct = default) => _connection.SetAuthEnabledAsync(enabled, ct);
Expand Down
2 changes: 2 additions & 0 deletions PowerControlHubApp/Services/IConfigConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public interface IConfigConnection
Task<bool> SetSdCardInitSpeedAsync(int speed, CancellationToken ct = default);
Task<int?> GetSdCardCsPinAsync(CancellationToken ct = default);
Task<bool> SetSdCardCsPinAsync(int pin, CancellationToken ct = default);
Task<(int DataPin, int ClockPin, int ResetPin)?> GetRtcPinsAsync(CancellationToken ct = default);
Task<bool> SetRtcPinsAsync(int dataPin, int clockPin, int resetPin, CancellationToken ct = default);
Task<AuthConfigModel> GetAuthConfigAsync(CancellationToken ct = default);
Task<bool> SetAuthEnabledAsync(bool enabled, CancellationToken ct = default);
Task<bool> SetAuthApiKeyAsync(string apiKey, CancellationToken ct = default);
Expand Down
10 changes: 10 additions & 0 deletions PowerControlHubApp/Services/PowerHubService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,16 @@ public Task<AuthConfigModel> GetAuthConfigAsync(CancellationToken ct = default)
return _configConnection.GetAuthConfigAsync(ct);
}

public Task<(int DataPin, int ClockPin, int ResetPin)?> GetRtcPinsAsync(CancellationToken ct = default)
{
return _configConnection.GetRtcPinsAsync(ct);
}

public Task<bool> SetRtcPinsAsync(int dataPin, int clockPin, int resetPin, CancellationToken ct = default)
{
return _configConnection.SetRtcPinsAsync(dataPin, clockPin, resetPin, ct);
}

public Task<bool> SetAuthEnabledAsync(bool enabled, CancellationToken ct = default)
{
return _configConnection.SetAuthEnabledAsync(enabled, ct);
Expand Down
180 changes: 180 additions & 0 deletions PowerControlHubApp/ViewModels/RtcSettingsViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using PowerControlHubApp.Models.Json;
using PowerControlHubApp.Services;
using System.Windows.Input;
using static PowerControlHubApp.Internal.Constants;

namespace PowerControlHubApp.ViewModels;

public sealed class RtcSettingsViewModel : BaseViewModel
{
private string _dataPin = string.Empty;
private string _clockPin = string.Empty;
private string _resetPin = string.Empty;
private bool _isRefreshing;
private bool _isSaving;

public RtcSettingsViewModel(PowerHubService service, LogService log)
: base(service, log)
{
RefreshCommand = new Command(async () => await RefreshAsync());
SaveAllCommand = new Command(async () => await SaveAllAsync());
}

public ICommand SaveAllCommand { get; }

public string DataPin
{
get => _dataPin;

set
{
_dataPin = value;
OnPropertyChanged();
}
}

public string ClockPin
{
get => _clockPin;

set
{
_clockPin = value;
OnPropertyChanged();
}
}

public string ResetPin
{
get => _resetPin;

set
{
_resetPin = value;
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 index = await Service.GetDashboardDataAsync();

MainThread.BeginInvokeOnMainThread(() =>
{
if (index?.Config?.Rtc != null)
{
DataPin = index.Config.Rtc.Dat == RtcPinDisabled ? string.Empty : index.Config.Rtc.Dat.ToString();
ClockPin = index.Config.Rtc.Clk == RtcPinDisabled ? string.Empty : index.Config.Rtc.Clk.ToString();
ResetPin = index.Config.Rtc.Rst == RtcPinDisabled ? string.Empty : index.Config.Rtc.Rst.ToString();
}

IsConnected = true;
StatusMessage = $"{RtcMsgRefreshed} {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;

try
{
bool pinsFailed = false;

if (int.TryParse(DataPin, out int data) && int.TryParse(ClockPin, out int clock) && int.TryParse(ResetPin, out int reset))
{
var ok = await Service.SetRtcPinsAsync(data, clock, reset);
pinsFailed = !ok;
}

await Service.SaveSettingsAsync();

MainThread.BeginInvokeOnMainThread(() =>
{
if (pinsFailed)
{
StatusMessage = RtcMsgSaveFailed;
}
else
{
StatusMessage = RtcMsgSaved;
}

OnPropertyChanged(nameof(HasStatusMessage));
});
}
catch
{
MainThread.BeginInvokeOnMainThread(() =>
{
StatusMessage = RtcMsgSaveFailed;
OnPropertyChanged(nameof(HasStatusMessage));
});
}
finally
{
IsSaving = false;
}
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822", Justification = "Called polymorphically from OnDisappearing")]
public void Cleanup()
{
}

protected override void OnDataFetched(IndexModel index)
{
}
}
Loading
Loading