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
2 changes: 2 additions & 0 deletions PowerControlHub/ConfigController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "IBluetoothRadio.h"
#include "IWifiController.h"
#include "SystemFunctions.h"
#include "DateTimeManager.h"

// from NextionIds.h
constexpr uint8_t ImageButtonColorBlue = 2;
Expand Down Expand Up @@ -252,6 +253,7 @@ ConfigResult ConfigController::setTimezoneOffset(const int8_t offset)
return ConfigResult::InvalidParameter;

_config->system.timezoneOffset = offset;
DateTimeManager::setTimezoneOffset(offset);
return ConfigResult::Success;
}

Expand Down
1 change: 1 addition & 0 deletions PowerControlHub/PowerControlHubApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ void PowerControlHubApp::setup(RemoteSensor** remoteSensors, uint8_t remoteSenso
Config* config = ConfigManager::getConfigPtr();

DateTimeManager::begin(config->rtc);
DateTimeManager::setTimezoneOffset(config->system.timezoneOffset);

// Build remote sensors from RemoteSensorsConfig (populated by ConfigManager::load()).
// Sensors are created dynamically here; ownership stays with PowerControlHubApp (_remoteSensors).
Expand Down
1 change: 1 addition & 0 deletions PowerControlHubApp/AppShell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public AppShell()
Routing.RegisterRoute(nameof(RelayDetailPage), typeof(RelayDetailPage));
Routing.RegisterRoute(nameof(ExternalSensorDetailPage), typeof(ExternalSensorDetailPage));
Routing.RegisterRoute(nameof(LocalSensorDetailPage), typeof(LocalSensorDetailPage));
Routing.RegisterRoute(nameof(TimeSettingsPage), typeof(TimeSettingsPage));
}
}
}
20 changes: 16 additions & 4 deletions PowerControlHubApp/Internal/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace PowerControlHubApp.Internal
{

#pragma warning disable CC0003

internal static class Constants
{
#if WINDOWS
Expand Down Expand Up @@ -108,15 +111,16 @@ internal static class Constants
public const int PortMax = 65535;
public const string MsgIpRequired = "IP address is required.";
public const string MsgInvalidPort = "Enter a valid port number (1–65535).";
public const string MessageNotConnected = "Not connected";

// Relay UI
public const string NoneString = "None";
public const string SavedOk = "✓ Saved successfully";
public const string SavedFailed = "⚠ One or more commands failed";
public const int ColorOptionNoneIndex = 6;
public static readonly string[] ColorOptionNames = new[] { ColorName_Blue, ColorName_Green, ColorName_Grey, ColorName_Orange, ColorName_Red, ColorName_Yellow, NoneString };
public static readonly string[] ActionOptionNames = new[] { ActionName_Default, ActionName_Horn, ActionName_NightRelay };
public static readonly string[] DefaultStateOptionNames = new[] { DefaultState_Off, DefaultState_On };
public static readonly string[] ColorOptionNames = [ColorName_Blue, ColorName_Green, ColorName_Grey, ColorName_Orange, ColorName_Red, ColorName_Yellow, NoneString];
public static readonly string[] ActionOptionNames = [ActionName_Default, ActionName_Horn, ActionName_NightRelay];
public static readonly string[] DefaultStateOptionNames = [DefaultState_Off, DefaultState_On];
public const string NavBack = "..";
public const string RelayPageTitle = "Relay";
public const string CheckMark = "✓";
Expand Down Expand Up @@ -191,19 +195,25 @@ internal static class Constants
public const string RouteSystemPins = "api/system/F15";
public const string RouteSystemGetDateTime = "api/system/F7";
public const string RouteSystemSetDateTime = "api/system/F6";
public const string RouteConfigTimezoneOffset = "api/config/C20";
public const string RouteWarnings = "api/warning/W5";
public const string ForwardSlash = "/";
public const string ResultSuccess = "success";
public const string RouteDashboardPage = "//DashboardPage";
public const string RouteSettingsPage = "//SettingsPage";
public const string RouteSystemPage = "//SystemPage";
public const string RouteTimeSettingsPage = "TimeSettingsPage";
public const string ConnectionTypeKey = "X-Connection-Type";
public const string ConnectionTypePersistent = "persistent";
public const int MaximumPermanentConnections = 2;
public const int SecondsSixty = 60;
public const int SecondsTen = 10;
public const int SecondsFive = 5;
public const int SecondsThree = 3;
private const int TZOffsetRawMinValue = 12;
private const int TZOffsetRawMaxValue = 14;
public const int TimezoneOffsetMin = -TZOffsetRawMinValue;
public const int TimezoneOffsetMax = TZOffsetRawMaxValue;
public const int SecondsTwo = 2;
public const string UserAgentKey = "User-Agent";
public const string UserAgentValue = "PowerControlHub/1.0";
Expand All @@ -225,7 +235,7 @@ internal static class Constants
public const string JsonSensorIndex = "i";
public const string JsonSensorId = "id";
public const string JsonSensorName = "n";
public const string JsonSensorMqttName = "mn";
public const string JsonSensorMqttName = "mn";
public const string JsonSensorMqttSlug = "ms";
public const string JsonSensorMqttType = "mt";
public const string JsonSensorMqttDeviceClass = "md";
Expand Down Expand Up @@ -408,4 +418,6 @@ internal static class Constants
public const string NullByte = "0x00";
public const string NibbleZero = "0x0";
}

#pragma warning restore CC0003
}
2 changes: 2 additions & 0 deletions PowerControlHubApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public static MauiApp CreateMauiApp()
builder.Services.AddTransient<ExternalSensorDetailViewModel>();
builder.Services.AddSingleton<LocalSensorConfigViewModel>();
builder.Services.AddTransient<LocalSensorDetailViewModel>();
builder.Services.AddTransient<TimeSettingsViewModel>();

// Pages
builder.Services.AddSingleton<DashboardPage>();
Expand All @@ -105,6 +106,7 @@ public static MauiApp CreateMauiApp()
builder.Services.AddTransient<ExternalSensorDetailPage>();
builder.Services.AddSingleton<LocalSensorConfigPage>();
builder.Services.AddTransient<LocalSensorDetailPage>();
builder.Services.AddTransient<TimeSettingsPage>();

#if DEBUG
builder.Logging.AddDebug();
Expand Down
6 changes: 6 additions & 0 deletions PowerControlHubApp/PowerControlHubApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
<MauiXaml Update="Views\LocalSensorDetailPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\NotConnectedBanner.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\RelayConfigPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
Expand Down Expand Up @@ -139,6 +142,9 @@
<MauiXaml Update="Views\Templates\WaterSensorTemplate.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\TimeSettingsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\TopBarView.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
Expand Down
39 changes: 39 additions & 0 deletions PowerControlHubApp/Services/ConfigConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,45 @@ public async Task<bool> SetDateTimeAsync(long unixTimestamp, CancellationToken c
}
}

public async Task<int?> GetTimezoneOffsetAsync(CancellationToken ct = default)
{
try
{
string json = await _client.GetStringAsync(RouteConfigTimezoneOffset, ct);
using JsonDocument doc = JsonDocument.Parse(json);

if (doc.RootElement.TryGetProperty(ResultSuccess, out var success) &&
success.GetBoolean() &&
doc.RootElement.TryGetProperty(JsonValueKey, out var v))
{
if (v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out int val))
return val;

if (v.ValueKind == JsonValueKind.String && int.TryParse(v.GetString(), out val))
return val;
}
}
catch
{
// fall through
}
return null;
}

public async Task<bool> SetTimezoneOffsetAsync(int offsetHours, CancellationToken ct = default)
{
try
{
string url = $"{RouteConfigTimezoneOffset}?{JsonValueKey}={offsetHours}";
HttpResponseMessage response = await _client.PostAsync(url, null, ct);
return response.IsSuccessStatusCode;
}
catch
{
return false;
}
}

private static async Task<bool> IsSuccessResponseAsync(HttpResponseMessage response, CancellationToken ct)
{
if (!response.IsSuccessStatusCode)
Expand Down
4 changes: 4 additions & 0 deletions PowerControlHubApp/Services/ConfigPoller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ private async Task RunLoopAsync(CancellationToken stoppingToken)

public Task<bool> SetDateTimeAsync(long unixTimestamp, CancellationToken ct = default) => _connection.SetDateTimeAsync(unixTimestamp, ct);

public Task<int?> GetTimezoneOffsetAsync(CancellationToken ct = default) => _connection.GetTimezoneOffsetAsync(ct);

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

public void Dispose()
{
if (_cts != null)
Expand Down
2 changes: 2 additions & 0 deletions PowerControlHubApp/Services/IConfigConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ public interface IConfigConnection
Task<bool> TriggerOtaInstallAsync(CancellationToken ct = default);
Task<DateTimeOffset?> GetDateTimeAsync(CancellationToken ct = default);
Task<bool> SetDateTimeAsync(long unixTimestamp, CancellationToken ct = default);
Task<int?> GetTimezoneOffsetAsync(CancellationToken ct = default);
Task<bool> SetTimezoneOffsetAsync(int offsetHours, CancellationToken ct = default);
}
}
20 changes: 20 additions & 0 deletions PowerControlHubApp/Services/PowerHubService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,24 @@ public Task<bool> TriggerOtaInstallAsync(CancellationToken ct = default)
{
return _configConnection.TriggerOtaInstallAsync(ct);
}

public Task<DateTimeOffset?> GetDateTimeAsync(CancellationToken ct = default)
{
return _configConnection.GetDateTimeAsync(ct);
}

public Task<bool> SetDateTimeAsync(long unixTimestamp, CancellationToken ct = default)
{
return _configConnection.SetDateTimeAsync(unixTimestamp, ct);
}

public Task<int?> GetTimezoneOffsetAsync(CancellationToken ct = default)
{
return _configConnection.GetTimezoneOffsetAsync(ct);
}

public Task<bool> SetTimezoneOffsetAsync(int offsetHours, CancellationToken ct = default)
{
return _configConnection.SetTimezoneOffsetAsync(offsetHours, ct);
}
}
9 changes: 2 additions & 7 deletions PowerControlHubApp/Services/TimeSyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public class TimeSyncService : IDisposable
public TimeSyncService(IConfigConnection configConnection, ILogger<TimeSyncService> log)
: this(configConnection, log,
TimeSpan.FromMinutes(TimeSyncIntervalMinutes),
TimeSyncDriftThresholdSeconds) { }
TimeSyncDriftThresholdSeconds)
{ }

public TimeSyncService(IConfigConnection configConnection, ILogger<TimeSyncService> log,
TimeSpan interval, int driftThresholdSeconds)
Expand Down Expand Up @@ -110,13 +111,7 @@ private async Task SyncTimeAsync(CancellationToken ct)
DateTimeOffset? deviceTime = await _configConnection.GetDateTimeAsync(ct);

if (deviceTime == null)
{
_log.LogInformation(LogTimeSyncNotSet);
long nowUnix = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
await _configConnection.SetDateTimeAsync(nowUnix, ct);
_log.LogInformation(LogTimeSyncSetting, 0, DateTimeOffset.UtcNow);
return;
}

DateTimeOffset localTime = DateTimeOffset.UtcNow;
double deltaSec = Math.Abs((localTime - deviceTime.Value).TotalSeconds);
Expand Down
3 changes: 3 additions & 0 deletions PowerControlHubApp/ViewModels/SystemViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,15 @@ public bool IsPinsRefreshing

public ICommand InstallFirmwareCommand { get; }

public ICommand NavigateToTimeSettingsCommand { get; }

public SystemViewModel(PowerHubService service, LogService log)
: base(service, log)
{
CheckForUpdateCommand = new Command(async () => await CheckForUpdateAsync());
RefreshPinsCommand = new Command(async () => await RefreshPinsAsync());
InstallFirmwareCommand = new Command(async () => await InstallFirmwareAsync(), () => CanInstallFirmware);
NavigateToTimeSettingsCommand = new Command(async () => await Shell.Current.GoToAsync(RouteTimeSettingsPage));
}

protected override void OnDataFetched(IndexModel index)
Expand Down
Loading
Loading