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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,4 @@ StaticElectrics/__vm/
/SmartFuseBox/__vm/Upload.vmps.xml
/SmartFuseBox/__vm/.SmartFuseBox.vsarduino.h
/LocalOnly
/.github/copilot-instructions.md
1 change: 1 addition & 0 deletions Docs/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ Example: `POST /api/sound/H3`
| `W2` — Warning Status | `W2:0x05=1` | Status of a single warning type. Param: `<WarningType>=<0\|1>`. |
| `W3` — Clear Warnings | `W3` | Clear all active warnings. No params. |
| `W4` — Set Warning Status | `W4:0x06=1` | Raise (`1`) or clear (`0`) a specific warning. Param: `<WarningType>=<0\|1>`. |
| `W5` — List Warning Text | `W5` | Returns one `W5:<hex>=<description>` frame per active warning and ends with `ACK:W5=ok`. Use this to get human-readable descriptions of all active warnings. No params. **WiFi response:** `{"success":true,"warnings":["desc1","desc2"]}`. |

### WiFi warning commands
Route: `/api/warning/{command}`
Expand Down
4 changes: 4 additions & 0 deletions Docs/Warnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ All warning commands are handled by `WarningCommandHandler`. Commands are availa
| Warning Status | `W2` | `<hex_flag>=<ignored>` | Returns `1` (active) or `0` (inactive) for a specific warning flag |
| Warnings Clear | `W3` | none | Clears all local and remote warnings |
| Warning Add/Clear | `W4` | `<hex_flag>=<0\|1>` | Raises (`1`) or clears (`0`) a specific warning by its hex bit value |
| Warning List Text | `W5` | none | Returns one `W5:<hex_flag>=<description>` frame per active warning, ending with `ACK:W5=ok`. Provides human-readable descriptions of all active warnings. |

**Examples:**

Expand All @@ -154,6 +155,9 @@ W2:0x00000004=? → ACK:W2=ok;0x00000004=0
W4:0x00000008=1 → raises HighCompassTemperature
W4:0x00000008=0 → clears HighCompassTemperature
W3 → clears all warnings
W5 → W5:0x00000004=Connection Lost To Fuse Box
W5:0x00000010=Low Battery
ACK:W5=ok
```

Warning types are passed as **hex bit-flag values** (e.g. `0x4` for `ConnectionLost`). The value must be a single set bit (power of 2); multi-bit values are rejected.
Expand Down
4 changes: 2 additions & 2 deletions PowerControlHub/PowerControlHub.vcxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions PowerControlHub/SystemDefinitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ constexpr char WarningsList[] = "W1";
constexpr char WarningStatus[] = "W2";
constexpr char WarningsClear[] = "W3";
constexpr char WarningsAdd[] = "W4";
constexpr char WarningsListText[] = "W5";

constexpr char SensorConfigMeta[] = "meta";
constexpr char SensorConfigGetAll[] = "S0";
Expand Down
34 changes: 33 additions & 1 deletion PowerControlHub/WarningCommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,38 @@ bool WarningCommandHandler::handleCommand(SerialCommandManager* sender, const ch
sendAckOk(sender, command, &param);
return true;
}
else if (SystemFunctions::commandMatches(command, WarningsListText) && paramCount == 0)
{
// Return list of active warnings as one frame per warning: W5:<hex_flag>=<text>
uint32_t activeWarnings = warningManager->getActiveWarningsMask();

for (uint8_t bit = 0; bit < 32; bit++)
{
if (activeWarnings & (1UL << bit))
{
const char* warningStr = getWarningString(bit);
if (warningStr != nullptr && warningStr[0] != '\0')
{
char flagsHex[16];
snprintf_P(flagsHex, sizeof(flagsHex), PSTR("0x%08lX"), (1UL << bit));

// Build params as "<hex>=<text>" and send as a command frame
char paramsBuf[128];
snprintf(paramsBuf, sizeof(paramsBuf), "%s=%s", flagsHex, warningStr);

// Send raw command frame to requester
if (sender != nullptr)
{
sender->sendCommand(WarningsListText, paramsBuf);
}
}
}
}

// Final ACK so callers know all lines have been sent
sendAckOk(sender, command);
return true;
}
else if (SystemFunctions::commandMatches(command, WarningsList) && paramCount == 0)
{
// Return the complete bitmask of active warnings as a single value
Expand Down Expand Up @@ -156,7 +188,7 @@ bool WarningCommandHandler::convertWarningTypeFromString(const char* str, Warnin
const char* const* WarningCommandHandler::supportedCommands(size_t& count) const
{
static const char* cmds[] = { WarningsActive, WarningsList, WarningStatus,
WarningsClear, WarningsAdd };
WarningsClear, WarningsAdd, WarningsListText };
count = sizeof(cmds) / sizeof(cmds[0]);
return cmds;
}
52 changes: 52 additions & 0 deletions PowerControlHub/WarningNetworkHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ CommandResult WarningNetworkHandler::handleRequest(const char* method,
return CommandResult::ok();
}

if (SystemFunctions::commandMatches(command, WarningsListText))
{
formatWarningsJsonList(responseBuffer, bufferSize);
return CommandResult::ok();
}

return CommandResult::error(InvalidCommandParameters);
}

Expand All @@ -75,3 +81,49 @@ void WarningNetworkHandler::formatWifiStatusJson(IWifiClient* client)
formatStatusJson(buffer, sizeof(buffer));
client->print(buffer);
}

void WarningNetworkHandler::formatWarningsJsonList(char* buffer, size_t size)
{
uint32_t activeWarnings = _warningManager->getActiveWarningsMask();

size_t pos = 0;
int written = snprintf(buffer + pos, (pos < size ? size - pos : 0), "\"success\":true,\"warnings\":[");
if (written < 0) return;
pos += (size_t)written;
bool first = true;

for (uint8_t bit = 0; bit < 32; bit++)
{
if (activeWarnings & (1UL << bit))
{
const char* warningStr = getWarningString(bit);

if (warningStr != nullptr && warningStr[0] != '\0')
{
if (!first)
{
if (pos < size - 1) buffer[pos++] = ',';
}

first = false;

// append quoted string (warningStr may contain spaces)
written = snprintf(buffer + pos, (pos < size ? size - pos : 0), "\"%s\"", warningStr);

if (written < 0)
break;

pos += (size_t)written;

if (pos >= size)
break;
}
}
}

if (pos < size - 2)
{
buffer[pos++] = ']';
buffer[pos] = '\0';
}
}
2 changes: 2 additions & 0 deletions PowerControlHub/WarningNetworkHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class WarningNetworkHandler : public INetworkCommandHandler

void formatWifiStatusJson(IWifiClient* client) override;

void formatWarningsJsonList(char* buffer, size_t size);

void formatStatusJson(char* buffer, size_t size);

CommandResult handleRequest(const char* method,
Expand Down
6 changes: 6 additions & 0 deletions PowerControlHubApp/AppShell.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,10 @@
Route="SettingsPage"
ContentTemplate="{DataTemplate views:SettingsPage}" />

<ShellContent
x:Name="SystemPage"
Title="System"
Route="SystemPage"
ContentTemplate="{DataTemplate views:SystemPage}" />

</Shell>
9 changes: 8 additions & 1 deletion PowerControlHubApp/Internal/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal static class Constants
public const string DefaultDeviceIpPort = "80";
public const string MessageNotConfigured = "Not configured — tap ⚙ to set device IP";
public const string MessageDeviceUnreachable = "Device unreachable";
public const string MessageNoActiveWarnings = "No active warnings";
public const string MessageToggleFailed = "Toggle failed — see log";
public const string LogOtaTrigger = "OTA: triggering firmware install…";
public const string OtaTriggerFailed = "OTA trigger failed";
Expand Down Expand Up @@ -101,6 +102,7 @@ internal static class Constants
public const string ThemeColor_LogTimestamp_Dark = "#555577";
public const string ThemeColor_LogText_Dark = "#aaaaaa";
public const string ThemeColor_Placeholder_Dark = "#555555";

// Networking / settings
public const int PortMin = 1;
public const int PortMax = 65535;
Expand Down Expand Up @@ -134,7 +136,7 @@ internal static class Constants
public const int NextionImageIdYellow = 7;
public const int NextionImageIdMin = NextionImageIdBlue;
public const int NextionImageIdMax = NextionImageIdYellow;
public const string ColorRelayPanelBlue = "#2255cc";
public const string ColorRelayPanelBlue = "#2255aa";
public const string ColorRelayPanelGreen = "#22aa44";
public const string ColorRelayPanelGrey = "#888888";
public const string ColorRelayPanelOrange = "#dd7722";
Expand Down Expand Up @@ -183,10 +185,12 @@ internal static class Constants
public const string RouteOtaUpdate = "api/system/F13";
public const string RouteUpdateOta = "api/system/F12?apply=1";
public const string RouteSystemPins = "api/system/F15";
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 ConnectionTypeKey = "X-Connection-Type";
public const string ConnectionTypePersistent = "persistent";
public const int MaximumPermanentConnections = 2;
Expand Down Expand Up @@ -382,5 +386,8 @@ internal static class Constants
public const string LogStartupMetaFetch = "Startup: first dashboard data received, fetching sensor meta on connection 2.";
public const string LogStartupMetaPopulated = "Startup: sensor meta cache populated.";
public const string LogStartupMetaAlready = "Startup: dashboard data already available, fetching sensor meta on connection 2.";

public const string NullByte = "0x00";
public const string NibbleZero = "0x0";
}
}
2 changes: 2 additions & 0 deletions PowerControlHubApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public static MauiApp CreateMauiApp()
builder.Services.AddSingleton<DashboardViewModel>();
builder.Services.AddSingleton<SettingsViewModel>();
builder.Services.AddSingleton<RelayConfigViewModel>();
builder.Services.AddSingleton<SystemViewModel>();
builder.Services.AddTransient<RelayDetailViewModel>();
builder.Services.AddSingleton<ExternalSensorConfigViewModel>();
builder.Services.AddTransient<ExternalSensorDetailViewModel>();
Expand All @@ -97,6 +98,7 @@ public static MauiApp CreateMauiApp()
builder.Services.AddSingleton<DashboardPage>();
builder.Services.AddSingleton<SettingsPage>();
builder.Services.AddSingleton<RelayConfigPage>();
builder.Services.AddSingleton<SystemPage>();
builder.Services.AddTransient<RelayDetailPage>();
builder.Services.AddSingleton<ExternalSensorConfigPage>();
builder.Services.AddTransient<ExternalSensorDetailPage>();
Expand Down
13 changes: 13 additions & 0 deletions PowerControlHubApp/Models/Json/WarningsListResponseModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;

namespace PowerControlHubApp.Models.Json
{
public sealed class WarningsListResponseModel
{
[JsonPropertyName("success")]
public bool Success { get; set; }

[JsonPropertyName("warnings")]
public List<string> Warnings { get; set; } = [];
}
}
1 change: 0 additions & 1 deletion PowerControlHubApp/Models/SensorTypeDescriptorModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text.Json.Serialization;
using static PowerControlHubApp.Internal.Constants;

namespace PowerControlHubApp.Models;

Expand Down
1 change: 0 additions & 1 deletion PowerControlHubApp/Platforms/Android/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Android.App;
using Android.Content.PM;
using Android.OS;

namespace PowerControlHubApp
{
Expand Down
9 changes: 9 additions & 0 deletions PowerControlHubApp/PowerControlHubApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@
<MauiXaml Update="Views\SettingsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\StatusBarView.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\SystemPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\Templates\BinaryPresenceSensorTemplate.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
Expand All @@ -133,6 +139,9 @@
<MauiXaml Update="Views\Templates\WaterSensorTemplate.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\TopBarView.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions PowerControlHubApp/Services/DashboardConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ public async Task<IndexModel> GetDashboardDataAsync(CancellationToken ct = defau
return result;
}

public async Task<List<string>> GetWarningsAsync(CancellationToken ct = default)
{
string json = await _client.GetStringAsync(RouteWarnings, ct);
WarningsListResponseModel resp = JsonSerializer.Deserialize<WarningsListResponseModel>(json, JsonOptions);
return resp?.Warnings ?? [];
}

private static SocketsHttpHandler CreateHandler()
{
var handler = new SocketsHttpHandler
Expand Down
2 changes: 1 addition & 1 deletion PowerControlHubApp/Services/DashboardPoller.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.ComponentModel;
using Microsoft.Extensions.Logging;
using PowerControlHubApp.Models.Json;
using System.ComponentModel;
using static PowerControlHubApp.Internal.Constants;

namespace PowerControlHubApp.Services;
Expand Down
4 changes: 4 additions & 0 deletions PowerControlHubApp/Services/IDashboardConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ namespace PowerControlHubApp.Services
public interface IDashboardConnection
{
bool IsConfigured { get; }

Task<IndexModel> GetDashboardDataAsync(CancellationToken ct = default);

Task<SystemPinsResponseModel> GetSystemPinsAsync(CancellationToken ct = default);

Task<List<string>> GetWarningsAsync(CancellationToken ct = default);
}
}
2 changes: 1 addition & 1 deletion PowerControlHubApp/Services/IDashboardProvider.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.ComponentModel;
using PowerControlHubApp.Models.Json;
using System.ComponentModel;

namespace PowerControlHubApp.Services;

Expand Down
6 changes: 3 additions & 3 deletions PowerControlHubApp/Services/LogService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Collections.ObjectModel;
using PowerControlHubApp.ViewModels;
using System.Collections.ObjectModel;

namespace PowerControlHubApp.Services;

Expand All @@ -24,8 +24,8 @@ public void Log(LogLevel level, string message)
var entry = new LogEntryViewModel
{
Timestamp = DateTime.Now,
Level = level,
Message = message
Level = level,
Message = message
};

MainThread.BeginInvokeOnMainThread(() =>
Expand Down
1 change: 0 additions & 1 deletion PowerControlHubApp/Services/MessageBus.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace PowerControlHubApp.Services
{
Expand Down
5 changes: 5 additions & 0 deletions PowerControlHubApp/Services/PowerHubService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public Task<IndexModel> GetDashboardDataAsync(CancellationToken ct = default)
return _dashboardConnection.GetDashboardDataAsync(ct);
}

public Task<List<string>> GetWarningsAsync(CancellationToken ct = default)
{
return _dashboardConnection.GetWarningsAsync(ct);
}

public Task<List<SensorTypeDescriptorModel>> GetSensorMetaAsync(CancellationToken ct = default)
{
return _configConnection.GetSensorMetaAsync(ct);
Expand Down
4 changes: 1 addition & 3 deletions PowerControlHubApp/Services/RelayStore.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using PowerControlHubApp.Models.Json;
using PowerControlHubApp.ViewModels;
using System.Collections.ObjectModel;

namespace PowerControlHubApp.Services;

Expand Down
Loading
Loading