diff --git a/Docs/Commands.md b/Docs/Commands.md index 9101052..62718bc 100644 --- a/Docs/Commands.md +++ b/Docs/Commands.md @@ -59,6 +59,7 @@ These are commands used to configure the system settings and can only be sent fr | `F12` — OTA Check / Apply | `F12` or `F12:apply=1` | Trigger an OTA firmware check against the latest GitHub release. Without params (or `apply=0`) checks only and returns current status. With `apply=1` downloads and applies the update if one is available (or queues apply if a check is already in progress). Response params: `v=` current firmware version, `av=` available version tag (empty if none found yet), `s=` OTA state string. **PCH only** — requires `OTA_AUTO_UPDATE`, ESP32 and WiFi in client mode. | | `F13` — OTA Status | `F13` | Query current OTA state without triggering a check. Response params: `v=` current firmware version, `av=` available version tag, `s=` OTA state string (`idle`, `checking`, `available`, `downloading`, `rebooting`, `failed`, `uptodate`), `auto=<0\|1>` whether auto-apply is enabled. **PCH only** — requires `OTA_AUTO_UPDATE`, ESP32 and WiFi in client mode. | | `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=` — AllowAdvisory: when `true`, advisory (strapping/UART) pins are permitted. `b=` — 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. | **OTA behaviour (F12 / F13):** diff --git a/PowerControlHub/PinGuard.h b/PowerControlHub/PinGuard.h index cd69226..5a9d668 100644 --- a/PowerControlHub/PinGuard.h +++ b/PowerControlHub/PinGuard.h @@ -46,6 +46,7 @@ #include #include "SystemDefinitions.h" +#include "SystemFunctions.h" // ─── Pin Use ───────────────────────────────────────────────────────────────── // Describes the intended use of a pin. Input-only GPIO are fine for Input, @@ -65,18 +66,19 @@ enum class PinUse : uint8_t // ─── Pin Category ───────────────────────────────────────────────────────────── enum class PinCategory : uint8_t { - Safe = 0, // unrestricted + Safe = 0, // unrestricted Advisory = 1, // risky; behaviour governed by PinGuardMode - Hard = 2, // always rejected for the given use + Hard = 2, // always rejected for the given use }; // ─── Validation Result ──────────────────────────────────────────────────────── enum class PinGuardResult : uint8_t { - Safe = 0, // pin is fine for the requested use + Safe = 0, // pin is fine for the requested use AdvisoryBlocked = 1, // advisory pin blocked (mode does not permit advisory) - HardBlocked = 2, // hard-blocked — must not be used for this purpose - Disabled = 3, // pin == PinDisabled (0xFF) — treated as not fitted + HardBlocked = 2, // hard-blocked — must not be used for this purpose + Disabled = 3, // pin == PinDisabled (0xFF) — treated as not fitted + InUse = 4, // pin is already in use by another configured function }; // ─── PinGuard Mode ──────────────────────────────────────────────────────────── @@ -84,9 +86,9 @@ enum class PinGuardResult : uint8_t // Configured via the F14 command. namespace PinGuardMode { - constexpr uint8_t None = 0x00; // strict — advisory pins are blocked + constexpr uint8_t None = 0x00; // strict — advisory pins are blocked constexpr uint8_t AllowAdvisory = 0x01; // advisory pins are permitted - constexpr uint8_t Bypass = 0x02; // skip all checks — always returns Safe + constexpr uint8_t Bypass = 0x02; // skip all checks — always returns Safe } // ─── Internal helpers — not for direct use ─────────────────────────────────── @@ -113,8 +115,8 @@ namespace PinGuardInternal struct PinTableEntry { - uint8_t gpio; - PinCategory category; + uint8_t gpio; + PinCategory category; }; #if defined(CONFIG_IDF_TARGET_ESP32S3) @@ -224,7 +226,7 @@ class PinGuard */ static bool isBlocked(PinGuardResult result) { - return result == PinGuardResult::HardBlocked || result == PinGuardResult::AdvisoryBlocked; + return result == PinGuardResult::HardBlocked || result == PinGuardResult::AdvisoryBlocked || result == PinGuardResult::InUse; } /** @@ -244,6 +246,10 @@ class PinGuard if (pin == PinDisabled) return PinGuardResult::Disabled; + // Check whether this pin is already assigned elsewhere in the configuration + if (SystemFunctions::pinInUse(pin)) + return PinGuardResult::InUse; + // Bypass: skip all checks if (_mode & PinGuardMode::Bypass) return PinGuardResult::Safe; @@ -309,6 +315,8 @@ class PinGuard return F("advisory (strapping/UART pin)"); case PinGuardResult::Disabled: return F("disabled (255)"); + case PinGuardResult::InUse: + return F("in use (already configured)"); default: return F("safe"); } diff --git a/PowerControlHub/SystemCommandHandler.cpp b/PowerControlHub/SystemCommandHandler.cpp index 23b96d5..f8f9e37 100644 --- a/PowerControlHub/SystemCommandHandler.cpp +++ b/PowerControlHub/SystemCommandHandler.cpp @@ -29,7 +29,7 @@ SystemCommandHandler::SystemCommandHandler(BroadcastManager* broadcaster, WarningManager* warningManager) - : SharedBaseCommandHandler(broadcaster, warningManager) + : SharedBaseCommandHandler(broadcaster, warningManager) { } @@ -39,389 +39,435 @@ SystemCommandHandler::~SystemCommandHandler() const char* const* SystemCommandHandler::supportedCommands(size_t& count) const { - static const char* cmds[] = { - SystemHeartbeatCommand, SystemInitialized, SystemFreeMemory, SystemCpuUsage, - SystemBluetoothStatus, SystemWifiStatus, SystemSetDateTime, SystemGetDateTime, - SystemSdCardPresent, SystemSdCardLogFileSize, SystemRtcDiagnostic, SystemUptime, - SystemCheckForUpdate, SystemOtaStatus, SystemPinGuardMode - }; - count = sizeof(cmds) / sizeof(cmds[0]); - return cmds; + static const char* cmds[] = { + SystemHeartbeatCommand, SystemInitialized, SystemFreeMemory, SystemCpuUsage, + SystemBluetoothStatus, SystemWifiStatus, SystemSetDateTime, SystemGetDateTime, + SystemSdCardPresent, SystemSdCardLogFileSize, SystemRtcDiagnostic, SystemUptime, + SystemCheckForUpdate, SystemOtaStatus, SystemPinGuardMode, SystemPinUsage + }; + count = sizeof(cmds) / sizeof(cmds[0]); + return cmds; } bool SystemCommandHandler::handleCommand(SerialCommandManager* sender, const char* command, const StringKeyValue params[], uint8_t paramCount) { - (void)params; - (void)paramCount; - - if (SystemFunctions::commandMatches(command, SystemHeartbeatCommand)) - { - // Use BaseConfigCommandHandler helpers to read named params - const char* tStr = getParamValue(params, paramCount, "t"); - if (tStr) - { - uint64_t timestamp = static_cast(strtoull(tStr, nullptr, 0)); - if (timestamp > 0) - { - DateTimeManager::setDateTime(timestamp); - } - } - - // Warnings parameter (w) is currently ignored but read here for completeness - const char* wStr = getParamValue(params, paramCount, "w"); - (void)wStr; - - sendAckOk(sender, command); - } - else if (SystemFunctions::commandMatches(command, SystemInitialized)) - { - sendAckOk(sender, command); - } - else if (SystemFunctions::commandMatches(command, SystemFreeMemory)) - { - StringKeyValue param; + (void)params; + (void)paramCount; + + if (SystemFunctions::commandMatches(command, SystemHeartbeatCommand)) + { + // Use BaseConfigCommandHandler helpers to read named params + const char* tStr = getParamValue(params, paramCount, "t"); + if (tStr) + { + uint64_t timestamp = static_cast(strtoull(tStr, nullptr, 0)); + if (timestamp > 0) + { + DateTimeManager::setDateTime(timestamp); + } + } + + // Warnings parameter (w) is currently ignored but read here for completeness + const char* wStr = getParamValue(params, paramCount, "w"); + (void)wStr; + + sendAckOk(sender, command); + } + else if (SystemFunctions::commandMatches(command, SystemInitialized)) + { + sendAckOk(sender, command); + } + else if (SystemFunctions::commandMatches(command, SystemFreeMemory)) + { + StringKeyValue param; strncpy(param.key, ValueParamName, sizeof(param.key)); snprintf_P(param.value, sizeof(param.value), PSTR("%u"), SystemFunctions::freeMemory()); - sendAckOk(sender, command, ¶m); - } + sendAckOk(sender, command, ¶m); + } else if (SystemFunctions::commandMatches(command, SystemCpuUsage)) - { - StringKeyValue param; - strncpy(param.key, ValueParamName, sizeof(param.key)); - snprintf_P(param.value, sizeof(param.value), PSTR("%u"), SystemCpuMonitor::getCpuUsage()); - sendAckOk(sender, command, ¶m); - } + { + StringKeyValue param; + strncpy(param.key, ValueParamName, sizeof(param.key)); + snprintf_P(param.value, sizeof(param.value), PSTR("%u"), SystemCpuMonitor::getCpuUsage()); + sendAckOk(sender, command, ¶m); + } else if (SystemFunctions::commandMatches(command, SystemBluetoothStatus)) - { + { Config* config = ConfigManager::getConfigPtr(); - bool enabled = false; - - if (config) + bool enabled = false; + + if (config) enabled = config->network.bluetoothEnabled; - - if (_broadcaster) - { + + if (_broadcaster) + { char value = enabled ? '1' : '0'; - StringKeyValue param = makeParam(ValueParamName, value); - sendAckOk(sender, command, ¶m); - } - } + StringKeyValue param = makeParam(ValueParamName, value); + sendAckOk(sender, command, ¶m); + } + } #if defined(WIFI_SUPPORT) - else if (SystemFunctions::commandMatches(command, SystemWifiStatus)) - { - Config* config = ConfigManager::getConfigPtr(); + else if (SystemFunctions::commandMatches(command, SystemWifiStatus)) + { + Config* config = ConfigManager::getConfigPtr(); - bool enabled = false; - char ipAddress[MaxIpAddressLength] = "0.0.0.0"; + bool enabled = false; + char ipAddress[MaxIpAddressLength] = "0.0.0.0"; char ssid[MaxSSIDLength] = ""; - int rssi = 0; + int rssi = 0; - if (config) - enabled = config->network.wifiEnabled; + if (config) + enabled = config->network.wifiEnabled; - // Get IP address from WifiController if available - if (_wifiController && enabled && _wifiController->isEnabled()) - { + // Get IP address from WifiController if available + if (_wifiController && enabled && _wifiController->isEnabled()) + { - _wifiController->getServer()->getIpAddress(ipAddress, MaxIpAddressLength); + _wifiController->getServer()->getIpAddress(ipAddress, MaxIpAddressLength); _wifiController->getServer()->getSSID(ssid, MaxSSIDLength); rssi = _wifiController->getServer()->getSignalStrength(); - } + } - if (_broadcaster) - { - constexpr uint8_t argCount = 4; + if (_broadcaster) + { + constexpr uint8_t argCount = 4; char enabledValue = enabled ? '1' : '0'; - StringKeyValue params[argCount] = { - makeParam(ValueParamName, enabledValue), - makeParam("ip", ipAddress), + StringKeyValue params[argCount] = { + makeParam(ValueParamName, enabledValue), + makeParam("ip", ipAddress), makeParam("ssid", ssid), - makeParam("rssi", rssi) - }; - sendAckOk(sender, command, params, argCount); - } - } + makeParam("rssi", rssi) + }; + sendAckOk(sender, command, params, argCount); + } + } #endif - else if (SystemFunctions::commandMatches(command, SystemSetDateTime) && paramCount == 1) - { - bool success = false; - - // Only supports Unix timestamp (all digits) - uint64_t timestamp = static_cast(strtoull(params[0].value, nullptr, 0)); - if (timestamp > 0) - { - DateTimeManager::setDateTime(timestamp); - success = true; - } - - if (success) - { - StringKeyValue param; + else if (SystemFunctions::commandMatches(command, SystemSetDateTime) && paramCount == 1) + { + bool success = false; + + // Only supports Unix timestamp (all digits) + uint64_t timestamp = static_cast(strtoull(params[0].value, nullptr, 0)); + if (timestamp > 0) + { + DateTimeManager::setDateTime(timestamp); + success = true; + } + + if (success) + { + StringKeyValue param; strncpy(param.key, ValueParamName, sizeof(param.key)); - DateTimeManager::formatDateTime(param.value, sizeof(param.value)); - sendAckOk(sender, command, ¶m); - } - else - { - _broadcaster->getComputerSerial()->sendDebug(command, F("Invalid Datetime")); - sendAckErr(sender, command, F("Invalid datetime format")); - } - - return true; - } + DateTimeManager::formatDateTime(param.value, sizeof(param.value)); + sendAckOk(sender, command, ¶m); + } + else + { + _broadcaster->getComputerSerial()->sendDebug(command, F("Invalid Datetime")); + sendAckErr(sender, command, F("Invalid datetime format")); + } + + return true; + } else if (SystemFunctions::commandMatches(command, SystemGetDateTime)) - { - if (DateTimeManager::isTimeSet()) - { - StringKeyValue param; - strncpy(param.key, ValueParamName, sizeof(param.key)); - DateTimeManager::formatDateTime(param.value, sizeof(param.value)); - sendAckOk(sender, command, ¶m); - } - else - { - sendAckErr(sender, command, F("Date/time not set")); - } - - return true; - } - else if (SystemFunctions::commandMatches(command, SystemSdCardPresent)) - { - bool present = false; + { + if (DateTimeManager::isTimeSet()) + { + StringKeyValue param; + strncpy(param.key, ValueParamName, sizeof(param.key)); + DateTimeManager::formatDateTime(param.value, sizeof(param.value)); + sendAckOk(sender, command, ¶m); + } + else + { + sendAckErr(sender, command, F("Date/time not set")); + } + + return true; + } + else if (SystemFunctions::commandMatches(command, SystemSdCardPresent)) + { + bool present = false; #if defined(SD_CARD_SUPPORT) - // Check SD card presence via MicroSdDriver - MicroSdDriver& sdDriver = MicroSdDriver::getInstance(); - present = sdDriver.isCardPresent(); + // Check SD card presence via MicroSdDriver + MicroSdDriver& sdDriver = MicroSdDriver::getInstance(); + present = sdDriver.isCardPresent(); #endif - char value = present ? '1' : '0'; - StringKeyValue param = makeParam(ValueParamName, value); - sendAckOk(sender, command, ¶m); - } - else if (SystemFunctions::commandMatches(command, SystemSdCardLogFileSize)) - { - uint32_t fileSize = 0; + char value = present ? '1' : '0'; + StringKeyValue param = makeParam(ValueParamName, value); + sendAckOk(sender, command, ¶m); + } + else if (SystemFunctions::commandMatches(command, SystemSdCardLogFileSize)) + { + uint32_t fileSize = 0; #if defined(SD_CARD_SUPPORT) - if (_sdCardLogger) - { - fileSize = _sdCardLogger->getCurrentLogFileSize(); - } + if (_sdCardLogger) + { + fileSize = _sdCardLogger->getCurrentLogFileSize(); + } #endif - StringKeyValue param; - strncpy(param.key, ValueParamName, sizeof(param.key)); - snprintf_P(param.value, sizeof(param.value), PSTR("%lu"), (unsigned long)fileSize); - sendAckOk(sender, command, ¶m); - } - else if (SystemFunctions::commandMatches(command, SystemRtcDiagnostic)) - { - char diagnosticMsg[64]; - - bool success = DateTimeManager::rtcDiagnostic(diagnosticMsg, sizeof(diagnosticMsg)); - StringKeyValue param; - strncpy(param.key, ValueParamName, sizeof(param.key)); - strncpy(param.value, diagnosticMsg, sizeof(param.value)); - - if (success) - { - sendAckOk(sender, command, ¶m); - } - else - { - sendAckErr(sender, command, param.value); - } - } - else if (SystemFunctions::commandMatches(command, SystemUptime)) - { - StringKeyValue param; - strncpy(param.key, ValueParamName, sizeof(param.key)); - TimeParts tp = SystemFunctions::msToTimeParts(SystemFunctions::millis64()); - SystemFunctions::formatTimeParts(param.value, sizeof(param.value), tp); - sendAckOk(sender, command, ¶m); - } + StringKeyValue param; + strncpy(param.key, ValueParamName, sizeof(param.key)); + snprintf_P(param.value, sizeof(param.value), PSTR("%lu"), (unsigned long)fileSize); + sendAckOk(sender, command, ¶m); + } + else if (SystemFunctions::commandMatches(command, SystemRtcDiagnostic)) + { + char diagnosticMsg[64]; + + bool success = DateTimeManager::rtcDiagnostic(diagnosticMsg, sizeof(diagnosticMsg)); + StringKeyValue param; + strncpy(param.key, ValueParamName, sizeof(param.key)); + strncpy(param.value, diagnosticMsg, sizeof(param.value)); + + if (success) + { + sendAckOk(sender, command, ¶m); + } + else + { + sendAckErr(sender, command, param.value); + } + } + else if (SystemFunctions::commandMatches(command, SystemUptime)) + { + StringKeyValue param; + strncpy(param.key, ValueParamName, sizeof(param.key)); + TimeParts tp = SystemFunctions::msToTimeParts(SystemFunctions::millis64()); + SystemFunctions::formatTimeParts(param.value, sizeof(param.value), tp); + sendAckOk(sender, command, ¶m); + } #if defined(OTA_AUTO_UPDATE) && defined(ESP32) && defined(WIFI_SUPPORT) - else if (SystemFunctions::commandMatches(command, SystemCheckForUpdate)) - { - if (_otaManager) - { - // apply=1 triggers a check and immediately applies the update if one is found. - const char* applyStr = getParamValue(params, paramCount, "apply"); - bool applyNow = applyStr && (applyStr[0] == '1'); - _otaManager->triggerCheck(applyNow); - - // Respond with the current state — the async result will be broadcast later. - char current[16]; - snprintf(current, sizeof(current), "v%u.%u.%u.%u", - FirmwareMajor, FirmwareMinor, FirmwarePatch, FirmwareBuild); - - const char* stateStr = "idle"; - - switch (_otaManager->getState()) - { - case OtaState::Idle: - stateStr = "triggered"; - break; - case OtaState::Checking: - stateStr = "checking"; - break; - case OtaState::UpdateAvailable: - stateStr = "available"; - break; - case OtaState::Downloading: - stateStr = "downloading"; - break; - case OtaState::Rebooting: - stateStr = "rebooting"; - break; - case OtaState::Failed: - stateStr = "failed"; - break; - case OtaState::UpToDate: - stateStr = "uptodate"; - break; - } - - constexpr uint8_t argCount = 3; - 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)); - strncpy(respParams[1].value, _otaManager->getAvailableVersion(), sizeof(respParams[1].value)); - strncpy(respParams[2].key, "s", sizeof(respParams[2].key)); - strncpy(respParams[2].value, stateStr, sizeof(respParams[2].value)); - sendAckOk(sender, command, respParams, argCount); - } - else - { - sendAckErr(sender, command, F("OTA not available")); - } - } - else if (SystemFunctions::commandMatches(command, SystemOtaStatus)) - { - char current[16]; - snprintf(current, sizeof(current), "v%u.%u.%u.%u", - FirmwareMajor, FirmwareMinor, FirmwarePatch, FirmwareBuild); - - const char* stateStr = "disabled"; - const char* avVersion = ""; - char autoApply = '0'; - - if (_otaManager) - { - switch (_otaManager->getState()) - { - case OtaState::Idle: stateStr = "idle"; break; - case OtaState::Checking: stateStr = "checking"; break; - case OtaState::UpdateAvailable: stateStr = "available"; break; - case OtaState::Downloading: stateStr = "downloading"; break; - case OtaState::Rebooting: stateStr = "rebooting"; break; - case OtaState::Failed: stateStr = "failed"; break; - case OtaState::UpToDate: stateStr = "uptodate"; break; - } - avVersion = _otaManager->getAvailableVersion(); - - SystemHeader* hdr = ConfigManager::getHeaderPtr(); - if (hdr && (hdr->reserved[0] & OtaFlagAutoApply)) - autoApply = '1'; - } - - constexpr uint8_t argCount = 4; - 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)); - strncpy(respParams[1].value, avVersion, sizeof(respParams[1].value)); - strncpy(respParams[2].key, "s", sizeof(respParams[2].key)); - strncpy(respParams[2].value, stateStr, sizeof(respParams[2].value)); - strncpy(respParams[3].key, "auto", sizeof(respParams[3].key)); - respParams[3].value[0] = autoApply; - respParams[3].value[1] = '\0'; - sendAckOk(sender, command, respParams, argCount); - } + else if (SystemFunctions::commandMatches(command, SystemCheckForUpdate)) + { + if (_otaManager) + { + // apply=1 triggers a check and immediately applies the update if one is found. + const char* applyStr = getParamValue(params, paramCount, "apply"); + bool applyNow = applyStr && (applyStr[0] == '1'); + _otaManager->triggerCheck(applyNow); + + // Respond with the current state — the async result will be broadcast later. + char current[16]; + snprintf(current, sizeof(current), "v%u.%u.%u.%u", + FirmwareMajor, FirmwareMinor, FirmwarePatch, FirmwareBuild); + + const char* stateStr = "idle"; + + switch (_otaManager->getState()) + { + case OtaState::Idle: + stateStr = "triggered"; + break; + case OtaState::Checking: + stateStr = "checking"; + break; + case OtaState::UpdateAvailable: + stateStr = "available"; + break; + case OtaState::Downloading: + stateStr = "downloading"; + break; + case OtaState::Rebooting: + stateStr = "rebooting"; + break; + case OtaState::Failed: + stateStr = "failed"; + break; + case OtaState::UpToDate: + stateStr = "uptodate"; + break; + } + + constexpr uint8_t argCount = 3; + 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)); + strncpy(respParams[1].value, _otaManager->getAvailableVersion(), sizeof(respParams[1].value)); + strncpy(respParams[2].key, "s", sizeof(respParams[2].key)); + strncpy(respParams[2].value, stateStr, sizeof(respParams[2].value)); + sendAckOk(sender, command, respParams, argCount); + } + else + { + sendAckErr(sender, command, F("OTA not available")); + } + } + else if (SystemFunctions::commandMatches(command, SystemOtaStatus)) + { + char current[16]; + snprintf(current, sizeof(current), "v%u.%u.%u.%u", + FirmwareMajor, FirmwareMinor, FirmwarePatch, FirmwareBuild); + + const char* stateStr = "disabled"; + const char* avVersion = ""; + char autoApply = '0'; + + if (_otaManager) + { + switch (_otaManager->getState()) + { + case OtaState::Idle: + stateStr = "idle"; + break; + + case OtaState::Checking: + stateStr = "checking"; + break; + + case OtaState::UpdateAvailable: + stateStr = "available"; + break; + + case OtaState::Downloading: + stateStr = "downloading"; + break; + + case OtaState::Rebooting: + stateStr = "rebooting"; + break; + + case OtaState::Failed: + stateStr = "failed"; + break; + + case OtaState::UpToDate: + stateStr = "uptodate"; + break; + } + + avVersion = _otaManager->getAvailableVersion(); + + SystemHeader* hdr = ConfigManager::getHeaderPtr(); + if (hdr && (hdr->reserved[0] & OtaFlagAutoApply)) + autoApply = '1'; + } + + constexpr uint8_t argCount = 4; + 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)); + strncpy(respParams[1].value, avVersion, sizeof(respParams[1].value)); + strncpy(respParams[2].key, "s", sizeof(respParams[2].key)); + strncpy(respParams[2].value, stateStr, sizeof(respParams[2].value)); + strncpy(respParams[3].key, "auto", sizeof(respParams[3].key)); + respParams[3].value[0] = autoApply; + respParams[3].value[1] = '\0'; + sendAckOk(sender, command, respParams, argCount); + } #endif // OTA_AUTO_UPDATE - else if (SystemFunctions::commandMatches(command, SystemPinGuardMode)) - { - if (paramCount > 0) - { - // Write mode: a= for AllowAdvisory, b= for Bypass - SystemHeader* hdr = ConfigManager::getHeaderPtr(); - if (hdr) - { - const char* aStr = getParamValue(params, paramCount, "a"); - const char* bStr = getParamValue(params, paramCount, "b"); - - if (aStr) - { - bool allow = (strcmp(aStr, "true") == 0 || strcmp(aStr, "1") == 0); - - if (allow) - hdr->pinGuardFlags |= PinGuardMode::AllowAdvisory; - else - hdr->pinGuardFlags &= ~PinGuardMode::AllowAdvisory; - } - - if (bStr) - { - bool bypass = (strcmp(bStr, "true") == 0 || strcmp(bStr, "1") == 0); - - if (bypass) - hdr->pinGuardFlags |= PinGuardMode::Bypass; - else - hdr->pinGuardFlags &= ~PinGuardMode::Bypass; - } - - ConfigManager::setPinGuardFlags(hdr->pinGuardFlags); - PinGuard::setMode(hdr->pinGuardFlags); - } - } - - // Read back current mode - SystemHeader* hdr = ConfigManager::getHeaderPtr(); - constexpr uint8_t argCount = 2; - 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'; - strncpy(respParams[1].key, "b", sizeof(respParams[1].key)); - respParams[1].value[0] = (hdr && (hdr->pinGuardFlags & PinGuardMode::Bypass)) ? '1' : '0'; - respParams[1].value[1] = '\0'; - sendAckOk(sender, command, respParams, argCount); - } - else - { - sendAckErr(sender, command, F("Unknown system command")); - } - - return true; + else if (SystemFunctions::commandMatches(command, SystemPinGuardMode)) + { + if (paramCount > 0) + { + // Write mode: a= for AllowAdvisory, b= for Bypass + SystemHeader* hdr = ConfigManager::getHeaderPtr(); + if (hdr) + { + const char* aStr = getParamValue(params, paramCount, "a"); + const char* bStr = getParamValue(params, paramCount, "b"); + + if (aStr) + { + bool allow = (strcmp(aStr, "true") == 0 || strcmp(aStr, "1") == 0); + + if (allow) + hdr->pinGuardFlags |= PinGuardMode::AllowAdvisory; + else + hdr->pinGuardFlags &= ~PinGuardMode::AllowAdvisory; + } + + if (bStr) + { + bool bypass = (strcmp(bStr, "true") == 0 || strcmp(bStr, "1") == 0); + + if (bypass) + hdr->pinGuardFlags |= PinGuardMode::Bypass; + else + hdr->pinGuardFlags &= ~PinGuardMode::Bypass; + } + + ConfigManager::setPinGuardFlags(hdr->pinGuardFlags); + PinGuard::setMode(hdr->pinGuardFlags); + } + } + + // Read back current mode + SystemHeader* hdr = ConfigManager::getHeaderPtr(); + constexpr uint8_t argCount = 2; + 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'; + strncpy(respParams[1].key, "b", sizeof(respParams[1].key)); + respParams[1].value[0] = (hdr && (hdr->pinGuardFlags & PinGuardMode::Bypass)) ? '1' : '0'; + respParams[1].value[1] = '\0'; + sendAckOk(sender, command, respParams, argCount); + } + else if (SystemFunctions::commandMatches(command, SystemPinUsage)) + { + uint8_t pins[64]; + uint8_t count = SystemFunctions::getUsedPins(pins, sizeof(pins)); + + StringKeyValue param; + strncpy(param.key, ValueParamName, sizeof(param.key)); + param.value[0] = '\0'; + + size_t pos = 0; + + for (uint8_t i = 0; i < count; ++i) + { + int written = snprintf(param.value + pos, sizeof(param.value) - pos, + "%s%u", (i > 0) ? "," : "", (unsigned)pins[i]); + + if (written <= 0 || (size_t)written >= sizeof(param.value) - pos) + break; + + pos += (size_t)written; + } + + sendAckOk(sender, command, ¶m); + return true; + } + else + { + sendAckErr(sender, command, F("Unknown system command")); + } + + return true; } #if defined(SD_CARD_SUPPORT) void SystemCommandHandler::setSdCardLogger(SdCardLogger* sdCardLogger) { - _sdCardLogger = sdCardLogger; + _sdCardLogger = sdCardLogger; } #endif #if defined(WIFI_SUPPORT) void SystemCommandHandler::setWifiController(WifiController* wifiController) -{ - _wifiController = wifiController; +{ + _wifiController = wifiController; } #endif #if defined(MQTT_SUPPORT) void SystemCommandHandler::setMqttController(MQTTController* mqttController) { - _mqttController = mqttController; + _mqttController = mqttController; } #endif #if defined(OTA_AUTO_UPDATE) && defined(ESP32) && defined(WIFI_SUPPORT) void SystemCommandHandler::setOtaManager(OtaManager* otaManager) { - _otaManager = otaManager; + _otaManager = otaManager; } #endif \ No newline at end of file diff --git a/PowerControlHub/SystemDefinitions.h b/PowerControlHub/SystemDefinitions.h index fe821ac..bbff785 100644 --- a/PowerControlHub/SystemDefinitions.h +++ b/PowerControlHub/SystemDefinitions.h @@ -42,6 +42,7 @@ constexpr char SystemUptime[] = "F11"; constexpr char SystemCheckForUpdate[] = "F12"; constexpr char SystemOtaStatus[] = "F13"; constexpr char SystemPinGuardMode[] = "F14"; +constexpr char SystemPinUsage[] = "F15"; constexpr char SystemPageDemo[] = "F150"; constexpr char RelayTurnAllOff[] = "R0"; diff --git a/PowerControlHub/SystemFunctions.cpp b/PowerControlHub/SystemFunctions.cpp index 7587712..8c37e1e 100644 --- a/PowerControlHub/SystemFunctions.cpp +++ b/PowerControlHub/SystemFunctions.cpp @@ -18,6 +18,7 @@ #include "Local.h" #include "SystemFunctions.h" #include "SystemDefinitions.h" +#include "ConfigManager.h" #if !defined(WIFI_SUPPORT) #include @@ -52,6 +53,97 @@ uint16_t SystemFunctions::stackAvailable() return (unsigned int)&v - (__brkval == 0 ? (unsigned int)&__heap_start : (unsigned int)__brkval); } +uint8_t SystemFunctions::getUsedPins(uint8_t* pins, uint8_t maxCount) +{ + if (!pins || maxCount == 0) + return 0; + + Config* cfg = ConfigManager::getConfigPtr(); + + if (!cfg) + return 0; + + uint8_t count = 0; + + auto addPin = [&](uint8_t p) + { + if (p == PinDisabled) + return; + + // Avoid duplicates + for (uint8_t i = 0; i < count; ++i) + { + if (pins[i] == p) + return; + } + + if (count < maxCount) + pins[count++] = p; + }; + + // Relay pins + for (uint8_t i = 0; i < ConfigRelayCount; ++i) + { + addPin(cfg->relay.relays[i].pin); + } + + // Sensor pins + uint8_t sensorCount = cfg->sensors.count; + if (sensorCount > ConfigMaxSensors) sensorCount = ConfigMaxSensors; + for (uint8_t i = 0; i < sensorCount; ++i) + { + for (uint8_t j = 0; j < ConfigMaxSensorPins; ++j) + { + addPin(cfg->sensors.sensors[i].pins[j]); + } + } + + // SD card CS pin + addPin(cfg->sdCard.csPin); + + // SPI pins + addPin(cfg->spiPins.sckPin); + addPin(cfg->spiPins.misoPin); + addPin(cfg->spiPins.mosiPin); + + // XpdzTone + addPin(cfg->xpdzTone.pin); + + // Hw479Rgb (r/g/b) + addPin(cfg->hw479Rgb.rPin); + addPin(cfg->hw479Rgb.gPin); + addPin(cfg->hw479Rgb.bPin); + + // RTC pins + addPin(cfg->rtc.dataPin); + addPin(cfg->rtc.clockPin); + addPin(cfg->rtc.resetPin); + + // Nextion pins + addPin(cfg->nextion.rxPin); + addPin(cfg->nextion.txPin); + + return count; +} + +bool SystemFunctions::pinInUse(uint8_t pin) +{ + if (pin == PinDisabled) + return false; + + // Buffer large enough to hold all possible configured pins + uint8_t usedPins[64]; + uint8_t count = SystemFunctions::getUsedPins(usedPins, sizeof(usedPins)); + + for (uint8_t i = 0; i < count; ++i) + { + if (usedPins[i] == pin) + return true; + } + + return false; +} + size_t SystemFunctions::freeMemory() { #if defined(ARDUINO_MEGA2560) diff --git a/PowerControlHub/SystemFunctions.h b/PowerControlHub/SystemFunctions.h index 3970bf9..1db4899 100644 --- a/PowerControlHub/SystemFunctions.h +++ b/PowerControlHub/SystemFunctions.h @@ -374,6 +374,28 @@ class SystemFunctions */ static void sanitizeJsonString(const char* input, char* output, size_t outputSize); + /** + * @brief Collect all GPIO pins currently assigned in the persistent configuration. + * + * Walks the in-memory Config and writes non-disabled pin numbers into the + * caller-provided buffer. Duplicate pin numbers are only reported once. + * + * @param pins Pointer to caller-provided buffer of uint8_t + * @param maxCount Maximum number of entries that can be written into pins + * @return Number of pins written into the buffer + */ + static uint8_t getUsedPins(uint8_t* pins, uint8_t maxCount); + + /** + * @brief Check whether a given GPIO pin number is present in the current configuration. + * + * Uses getUsedPins internally to collect configured pins and searches the list. + * + * @param pin GPIO pin number to check + * @return true if pin is used in configuration, false otherwise + */ + static bool pinInUse(uint8_t pin); + /** * @brief Format a float as a JSON number, emitting null for non-finite values. * diff --git a/PowerControlHub/SystemNetworkHandler.cpp b/PowerControlHub/SystemNetworkHandler.cpp index 269d290..541c378 100644 --- a/PowerControlHub/SystemNetworkHandler.cpp +++ b/PowerControlHub/SystemNetworkHandler.cpp @@ -53,6 +53,37 @@ CommandResult SystemNetworkHandler::handleRequest(const char* method, formatStatusJson(responseBuffer, bufferSize); return CommandResult::ok(); } + else if (SystemFunctions::commandMatches(command, SystemPinUsage)) + { + uint8_t pins[64]; + uint8_t count = SystemFunctions::getUsedPins(pins, sizeof(pins)); + + size_t pos = 0; + int written = snprintf(responseBuffer + pos, bufferSize - pos, + "\"success\":true,\"command\":\"%s\",\"pins\":[", command); + + if (written > 0) + pos += (size_t)written; + + for (uint8_t i = 0; i < count && pos < bufferSize; ++i) + { + written = snprintf(responseBuffer + pos, bufferSize - pos, + "%s%u", (i > 0) ? "," : "", (unsigned)pins[i]); + + if (written > 0) + pos += (size_t)written; + else + break; + } + + if (pos < bufferSize) + { + responseBuffer[pos++] = ']'; + responseBuffer[pos] = '\0'; + } + + return CommandResult::ok(); + } else { return CommandResult::error(InvalidCommandParameters);