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
8 changes: 6 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ dotnet_diagnostic.CC0008.severity = error
dotnet_diagnostic.CC0009.severity = error
dotnet_diagnostic.CC0010.severity = error


dotnet_diagnostic.IDE0028.severity = error
dotnet_diagnostic.IDE0290.severity = none
dotnet_diagnostic.IDE0305.severity = error


dotnet_diagnostic.CS0144.severity = error
dotnet_diagnostic.CS8632.severity = error

dotnet_diagnostic.CA1822.severity = error

dotnet_diagnostic.IDE0290.severity = none
dotnet_diagnostic.CA1822.severity = error
7 changes: 4 additions & 3 deletions Docs/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ These are commands used to configure the system settings and can only be sent fr
| `F14` — PinGuard Mode | `F14` or `F14:a=true` or `F14:a=false;b=false` | Read or write the persistent PinGuard mode flags stored in `SystemHeader::pinGuardFlags`. **Params:** `a=<bool>` — AllowAdvisory: when `true`, advisory (strapping/UART) pins are permitted. `b=<bool>` — Bypass: when `true`, all PinGuard checks are skipped (no validation at all). If both `a` and `b` are `true`, Bypass takes precedence and the system returns `Safe` immediately. Changes are persisted immediately via `saveHeader()` and take effect at runtime without reboot. **Response:** `a=<0\|1>` current AllowAdvisory state, `b=<0\|1>` current Bypass state. No params = read-only. |
| `F15` — Pin Usage | `F15` | Returns a list of all GPIO pins currently assigned in configuration. Pins set to `0xFF` (disabled / not fitted) are omitted. **Serial response:** `v=pin1,pin2,...` (single frame). **WiFi response:** JSON object with a `pins` array. No params. |
| `F16` — Pin Restrictions | `F16` | Returns the compile-time pin restriction table for the current target MCU. Pins are classified as `Hard` (always blocked, e.g. flash-reserved, PSRAM) or `Advisory` (strapping, UART0, USB, JTAG — usable only when PinGuard AllowAdvisory is enabled). **Serial response:** one line per pin as `pin - category` followed by `ACK:F16=ok`. **WiFi response:** JSON object with `"hard"` and `"advisory"` integer arrays. No params. |
| `F17` — Location Types | `F17` | Returns all `LocationType` enum values as descriptors. Each entry has `id` (uint8), `type` (`boat` or `other`), and `desc` (human-readable name). **Serial response:** one line per descriptor as `F17:id=<id>;type=<boat\|other>;desc=<name>` followed by `ACK:F17=ok`. **WiFi response:** JSON object with `"success"`, `"command"`, and `"locations"` array of `{"id", "type", "description"}` objects. No params. |


**OTA behaviour (F12 / F13):**
Expand Down Expand Up @@ -103,9 +104,9 @@ Example: `GET /api/system/F2`
| `C18` — RTC Pins | `C18:dat=4;clk=5;rst=6` | Set DS1302 RTC pins. Use `255` for any pin not fitted. Call `C0` to persist. |
| `C19` — Network Authentication | `C19:e=1;k=MyApiKey;h=MyHmacKey` or `C19:g=1` | Configure WiFi API authentication. **Params:** `e=<0\|1>` enable/disable auth (disabled by default). `k=<key>` set API key (max 31 chars). `h=<key>` set HMAC-SHA256 key (max 31 chars). `g=1` auto-generate device-unique keys from the WiFi MAC address. No params returns current state as `e=<0\|1>;k=<apiKey>;h=<hmacKey>`. Call `C0` to persist. When enabled, all `/api/*` endpoints require either an `X-API-Key` header matching the configured key, or valid `X-Auth-Timestamp` + `X-Auth-Signature` HMAC headers. See [Network Authentication](#network-authentication) below. |
| `C20` — Timezone Offset | `C20:v=-5` | Set UTC timezone offset in hours. Valid range: −12 to +14. |
| `C21` — MMSI | `C21:123456789` | Set 9-digit Maritime Mobile Service Identity. Value directly. |
| `C22` — Call Sign | `C22:ABCD123` | Set location call sign. Value directly, truncated to max length. |
| `C23` — Home Port | `C23:Miami` | Set location home port. Value directly, truncated to max length. |
| `C21` — MMSI | `C21:v=123456789` | Set 9-digit Maritime Mobile Service Identity. Value directly. |
| `C22` — Call Sign | `C22:v=ABCD123` | Set location call sign. Value directly, truncated to max length. |
| `C23` — Home Port | `C23:v=Miami` | Set location home port. Value directly, truncated to max length. |
| `C24` — LED Color | `C24:t=0;c=0;r=255;g=50;b=213` | Set LED RGB color. `t`: `0`=day, `1`=night. `c`: `0`=good, `1`=bad. RGB values 0–255. |
| `C25` — LED Brightness | `C25:t=0;b=75` | Set LED brightness 0–100. `t`: `0`=day, `1`=night. |
| `C26` — LED Auto Switch | `C26:v=1` | Enable/disable automatic day/night LED switching. |
Expand Down
4 changes: 0 additions & 4 deletions PowerControlHub/ConfigController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,6 @@ ConfigResult ConfigController::setLocationType(const uint8_t locationlType)
{
if (_config == nullptr)
return ConfigResult::InvalidConfig;

if (locationlType > static_cast<uint8_t>(LocationType::Yacht))
return ConfigResult::InvalidParameter;

_config->location.locationType = static_cast<LocationType>(locationlType);
updateSoundControllerConfig();
return ConfigResult::Success;
Expand Down
34 changes: 31 additions & 3 deletions PowerControlHub/ConfigNetworkHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,16 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method,
// C21 - Set MMSI
if (paramCount >= 1)
{
result = _configController->setMmsi(params[0].value);
const char* mmsi = getParamValue(params, paramCount, "v");

if (mmsi == nullptr || strlen(mmsi) != 9 || !SystemFunctions::isAllDigits(mmsi))
{
result = ConfigResult::InvalidParameter;
}
else
{
result = _configController->setMmsi(mmsi);
}
}
else
{
Expand All @@ -615,7 +624,16 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method,
// C22 - Set call sign
if (paramCount >= 1)
{
result = _configController->setCallSign(params[0].value);
const char* callSign = getParamValue(params, paramCount, "v");

if (callSign == nullptr || strlen(callSign) > ConfigCallSignLength)
{
result = ConfigResult::InvalidParameter;
}
else
{
result = _configController->setCallSign(callSign);
}
}
else
{
Expand All @@ -627,7 +645,16 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method,
// C23 - Set home port
if (paramCount >= 1)
{
result = _configController->setHomePort(params[0].value);
const char* homePort = getParamValue(params, paramCount, "v");

if (homePort == nullptr || strlen(homePort) > ConfigHomePortLength)
{
result = ConfigResult::InvalidParameter;
}
else
{
result = _configController->setHomePort(homePort);
}
}
else
{
Expand Down Expand Up @@ -1005,6 +1032,7 @@ CommandResult ConfigNetworkHandler::handleRequest(const char* method,

if (result == ConfigResult::Success)
{
formatJsonResponse(responseBuffer, bufferSize, true, "");
return CommandResult::ok();
}

Expand Down
33 changes: 33 additions & 0 deletions PowerControlHub/LocationConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once
#include "Config.h"

enum class LocationTypeSubDescriptor : uint8_t
{
Boat = 0x00,
Other = 0x01
};

struct LocationTypeDescriptor
{
uint8_t id;
LocationTypeSubDescriptor subType;
const char* description;
};

// Indexed by LocationType enum value
constexpr LocationTypeDescriptor LocationTypeDescriptors[] = {
[static_cast<size_t>(LocationType::Power)] = { static_cast<uint8_t>(LocationType::Power), LocationTypeSubDescriptor::Boat, "Power boat" },
[static_cast<size_t>(LocationType::Sail)] = { static_cast<uint8_t>(LocationType::Sail), LocationTypeSubDescriptor::Boat, "Sailing boat" },
[static_cast<size_t>(LocationType::Fishing)] = { static_cast<uint8_t>(LocationType::Fishing), LocationTypeSubDescriptor::Boat, "Fishing boat" },
[static_cast<size_t>(LocationType::Yacht)] = { static_cast<uint8_t>(LocationType::Yacht), LocationTypeSubDescriptor::Boat, "Yacht" },
[static_cast<size_t>(LocationType::Shed)] = { static_cast<uint8_t>(LocationType::Shed), LocationTypeSubDescriptor::Other, "Shed" },
[static_cast<size_t>(LocationType::Basement)] = { static_cast<uint8_t>(LocationType::Basement), LocationTypeSubDescriptor::Other, "Basement" },
[static_cast<size_t>(LocationType::Workshop)] = { static_cast<uint8_t>(LocationType::Workshop), LocationTypeSubDescriptor::Other, "Workshop" },
[static_cast<size_t>(LocationType::Garage)] = { static_cast<uint8_t>(LocationType::Garage), LocationTypeSubDescriptor::Other, "Garage" },
[static_cast<size_t>(LocationType::Bedroom)] = { static_cast<uint8_t>(LocationType::Bedroom), LocationTypeSubDescriptor::Other, "Bedroom" },
[static_cast<size_t>(LocationType::Office)] = { static_cast<uint8_t>(LocationType::Office), LocationTypeSubDescriptor::Other, "Office" },
// Note: LocationType::Other has value 0xFF and is not included in the indexed array
};

static_assert(std::size(LocationTypeDescriptors) == static_cast<size_t>(LocationType::Office) + 1,
"LocationTypeDescriptors must cover all LocationType enum values up to Office. Update descriptors when enum changes.");
11 changes: 10 additions & 1 deletion PowerControlHub/PowerControlHub.vcxproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
Expand Down Expand Up @@ -177,6 +177,7 @@
<ClInclude Include="IWifiClient.h" />
<ClInclude Include="IWifiController.h" />
<ClInclude Include="IWifiRadio.h" />
<ClInclude Include="LocationConfig.h" />
<ClInclude Include="MicroSdDriver.h" />
<ClInclude Include="MQTTConfigCommandHandler.h" />
<ClInclude Include="MQTTHandler.h" />
Expand Down Expand Up @@ -294,6 +295,14 @@
<DeploymentContent>true</DeploymentContent>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PowerControlHubApp\Models\Json\SystemLocationTypesResponseModel.cs" />
<None Include="PowerControlHubApp\ViewModels\LocationSettingsViewModel.cs" />
<None Include="PowerControlHubApp\Views\LocationSettingsPage.xaml.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="PowerControlHubApp\Views\LocationSettingsPage.xaml" />
</ItemGroup>
<PropertyGroup>
<DebuggerFlavor>VisualMicroDebugger</DebuggerFlavor>
</PropertyGroup>
Expand Down
11 changes: 11 additions & 0 deletions PowerControlHub/PowerControlHub.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -717,5 +717,16 @@
<ClInclude Include="MQTTNetworkHandler.h">
<Filter>Header Files\NetworkCommandHandlers</Filter>
</ClInclude>
<ClInclude Include="LocationConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="PowerControlHubApp\Models\Json\SystemLocationTypesResponseModel.cs" />
<None Include="PowerControlHubApp\ViewModels\LocationSettingsViewModel.cs" />
<None Include="PowerControlHubApp\Views\LocationSettingsPage.xaml.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="PowerControlHubApp\Views\LocationSettingsPage.xaml" />
</ItemGroup>
</Project>
46 changes: 34 additions & 12 deletions PowerControlHub/SystemCommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "ConfigManager.h"
#include "PinGuard.h"
#include "DateTimeManager.h"
#include "LocationConfig.h"
#if defined(WIFI_SUPPORT)
#include "WifiController.h"
#endif
Expand All @@ -43,7 +44,8 @@ const char* const* SystemCommandHandler::supportedCommands(size_t& count) const
SystemHeartbeatCommand, SystemInitialized, SystemFreeMemory, SystemCpuUsage,
SystemBluetoothStatus, SystemWifiStatus, SystemSetDateTime, SystemGetDateTime,
SystemSdCardPresent, SystemSdCardLogFileSize, SystemRtcDiagnostic, SystemUptime,
SystemCheckForUpdate, SystemOtaStatus, SystemPinGuardMode, SystemPinUsage, SystemPinRestrictions
SystemCheckForUpdate, SystemOtaStatus, SystemPinGuardMode, SystemPinUsage,
SystemPinRestrictions, SystemLocationTypes
};
count = sizeof(cmds) / sizeof(cmds[0]);
return cmds;
Expand Down Expand Up @@ -79,14 +81,14 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
}
else if (SystemFunctions::commandMatches(command, SystemFreeMemory))
{
StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
snprintf_P(param.value, sizeof(param.value), PSTR("%u"), SystemFunctions::freeMemory());
sendAckOk(sender, command, &param);
}
else if (SystemFunctions::commandMatches(command, SystemCpuUsage))
{
StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
snprintf_P(param.value, sizeof(param.value), PSTR("%u"), SystemCpuMonitor::getCpuUsage());
sendAckOk(sender, command, &param);
Expand Down Expand Up @@ -157,7 +159,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha

if (success)
{
StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
DateTimeManager::formatDateTime(param.value, sizeof(param.value));
sendAckOk(sender, command, &param);
Expand All @@ -174,7 +176,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
{
if (DateTimeManager::isTimeSet())
{
StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
DateTimeManager::formatDateTime(param.value, sizeof(param.value));
sendAckOk(sender, command, &param);
Expand Down Expand Up @@ -211,7 +213,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
}
#endif

StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
snprintf_P(param.value, sizeof(param.value), PSTR("%lu"), (unsigned long)fileSize);
sendAckOk(sender, command, &param);
Expand All @@ -221,7 +223,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
char diagnosticMsg[64];

bool success = DateTimeManager::rtcDiagnostic(diagnosticMsg, sizeof(diagnosticMsg));
StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
strncpy(param.value, diagnosticMsg, sizeof(param.value));

Expand All @@ -236,7 +238,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
}
else if (SystemFunctions::commandMatches(command, SystemUptime))
{
StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
TimeParts tp = SystemFunctions::msToTimeParts(SystemFunctions::millis64());
SystemFunctions::formatTimeParts(param.value, sizeof(param.value), tp);
Expand Down Expand Up @@ -285,7 +287,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
}

constexpr uint8_t argCount = 3;
StringKeyValue respParams[argCount];
StringKeyValue respParams[argCount]{};
strncpy(respParams[0].key, "v", sizeof(respParams[0].key));
strncpy(respParams[0].value, current, sizeof(respParams[0].value));
strncpy(respParams[1].key, "av", sizeof(respParams[1].key));
Expand Down Expand Up @@ -350,7 +352,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
}

constexpr uint8_t argCount = 4;
StringKeyValue respParams[argCount];
StringKeyValue respParams[argCount]{};
strncpy(respParams[0].key, "v", sizeof(respParams[0].key));
strncpy(respParams[0].value, current, sizeof(respParams[0].value));
strncpy(respParams[1].key, "av", sizeof(respParams[1].key));
Expand Down Expand Up @@ -402,7 +404,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
// Read back current mode
SystemHeader* hdr = ConfigManager::getHeaderPtr();
constexpr uint8_t argCount = 2;
StringKeyValue respParams[argCount];
StringKeyValue respParams[argCount]{};
strncpy(respParams[0].key, "a", sizeof(respParams[0].key));
respParams[0].value[0] = (hdr && (hdr->pinGuardFlags & PinGuardMode::AllowAdvisory)) ? '1' : '0';
respParams[0].value[1] = '\0';
Expand All @@ -416,7 +418,7 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
uint8_t pins[64];
uint8_t count = SystemFunctions::getUsedPins(pins, sizeof(pins));

StringKeyValue param;
StringKeyValue param{};
strncpy(param.key, ValueParamName, sizeof(param.key));
param.value[0] = '\0';

Expand Down Expand Up @@ -455,6 +457,26 @@ bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const cha
sendAckOk(sender, command);
return true;
}
else if (SystemFunctions::commandMatches(command, SystemLocationTypes))
{
// Send location type descriptors one per line for serial clients
size_t cnt = sizeof(LocationTypeDescriptors) / sizeof(LocationTypeDescriptors[0]);
for (size_t i = 0; i < cnt; ++i)
{
const auto& d = LocationTypeDescriptors[i];
if (d.description == nullptr)
continue;
char buffer[128];
snprintf(buffer, sizeof(buffer), "id=%u;type=%" PRIu8 ";desc=%s",
(unsigned)d.id,
static_cast<uint8_t>(d.subType),
d.description);
sender->sendCommand(SystemLocationTypes, buffer);
}

sendAckOk(sender, command);
return true;
}
else
{
sendAckErr(sender, command, F("Unknown system command"));
Expand Down
1 change: 1 addition & 0 deletions PowerControlHub/SystemDefinitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ constexpr char SystemOtaStatus[] = "F13";
constexpr char SystemPinGuardMode[] = "F14";
constexpr char SystemPinUsage[] = "F15";
constexpr char SystemPinRestrictions[] = "F16";
constexpr char SystemLocationTypes[] = "F17";
constexpr char SystemPageDemo[] = "F150";

constexpr char RelayTurnAllOff[] = "R0";
Expand Down
Loading
Loading