Fix Full Companion terminal diagnostics
This commit is contained in:
1 parent
e666bbc6a8
commit
4c93df9d57
7 files changed
+176
-9
No files matched your search
@@ -47,6 +47,8 @@ over the normal binary USB, BLE, or TCP connection:
|
||||
|
||||
| Command | Availability |
|
||||
|---|---|
|
||||
| [`board`](cli_commands.md#show-the-hardware-name) | Every Companion text terminal and command `0x42` |
|
||||
| [`memory`](#memory) | ESP32 Full Companion text terminal and command `0x42` |
|
||||
| [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Companion with a supported radio |
|
||||
| [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Companion on a board with controllable LoRa FEM LNA |
|
||||
| [`get/set wifi.powersave`](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server) | ESP32 WiFi Companion; active transport constraints still apply |
|
||||
|
||||
@@ -372,9 +372,11 @@ The terminal supports Companion chat commands, including `channels`,
|
||||
`channel <name-or-slot> <message>`, remote administration with
|
||||
`login <admin-password>` and `cmd <remote-command>`, and routed
|
||||
`trace [recipient-name-or-prefix]`, plus local `ota`, `tempradio`, and
|
||||
`normalradio` controls. ESP32 builds also provide local
|
||||
WiFi credential, status, WebConfig, CLI-tab, and power-save controls. Every
|
||||
Full Companion provides persistent `get/set usb.logging` and starts with
|
||||
`normalradio` controls. `board` reports the exact hardware target. ESP32 Full
|
||||
Companion also provides `memory`, which reports heap, internal allocation,
|
||||
PSRAM, and offline-queue usage; it is omitted from non-ESP32 help. ESP32 builds
|
||||
also provide local WiFi credential, status, WebConfig, and power-save controls.
|
||||
Every Full Companion provides persistent `get/set usb.logging` and starts with
|
||||
logging off on a fresh installation.
|
||||
|
||||
### ESP32 single USB serial port
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#include "helpers/radiolib/RxBoostedGainDefaults.h"
|
||||
#include "helpers/radiolib/CadTiming.h"
|
||||
|
||||
#if defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
|
||||
#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
#include <helpers/CompanionTerminalDiagnostics.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP32_PLATFORM) \
|
||||
&& (defined(BOARD_HAS_PSRAM) || COMPANION_FEATURE_MEMORY_DIAGNOSTICS)
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
|
||||
@@ -2043,6 +2048,31 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
if (!command || !reply || reply_size == 0) return false;
|
||||
while (*command == ' ') command++;
|
||||
|
||||
if (strcmp(command, "board") == 0) {
|
||||
const char* hardware_name = board.getManufacturerName();
|
||||
snprintf(reply, reply_size, "%s",
|
||||
hardware_name != NULL ? hardware_name : "Unknown hardware");
|
||||
return true;
|
||||
}
|
||||
|
||||
#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
if (strcmp(command, "memory") == 0) {
|
||||
const mesh::CompanionMemoryDiagnostics diagnostics = {
|
||||
(uint32_t)ESP.getFreeHeap(),
|
||||
(uint32_t)ESP.getMinFreeHeap(),
|
||||
(uint32_t)ESP.getMaxAllocHeap(),
|
||||
(uint32_t)heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
(uint32_t)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
|
||||
(uint32_t)ESP.getFreePsram(),
|
||||
(uint32_t)ESP.getPsramSize(),
|
||||
offline_queue_len,
|
||||
getOfflineQueueCapacity()
|
||||
};
|
||||
mesh::formatCompanionMemoryDiagnostics(reply, reply_size, diagnostics);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (handleCadCommand(command, reply, reply_size)) return true;
|
||||
|
||||
if (strcmp(command, "get radio.rxgain") == 0) {
|
||||
@@ -5979,6 +6009,10 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
FIRMWARE_VERSION, (unsigned)FIRMWARE_VER_CODE, FIRMWARE_BUILD_DATE);
|
||||
} else if (strcmp(command, "help") == 0) {
|
||||
terminalOutput().print("Commands:\r\n");
|
||||
terminalOutput().print(" board\r\n");
|
||||
#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
terminalOutput().print(" memory\r\n");
|
||||
#endif
|
||||
terminalOutput().print(" get display.rotation\r\n");
|
||||
terminalOutput().print(" set display.rotation <0|90|180|270>\r\n");
|
||||
terminalOutput().print(" set {name|lat|lon|freq|tx|af} {value}\r\n");
|
||||
@@ -6220,11 +6254,6 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(command, "board") == 0) {
|
||||
snprintf(reply, reply_capacity, "%s", board.getManufacturerName());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(command, "ver") == 0) {
|
||||
snprintf(reply, reply_capacity, "%s (Build: %s)", FIRMWARE_VERSION,
|
||||
FIRMWARE_BUILD_DATE);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
struct CompanionMemoryDiagnostics {
|
||||
uint32_t heap_free;
|
||||
uint32_t heap_min;
|
||||
uint32_t heap_max_alloc;
|
||||
uint32_t internal_free;
|
||||
uint32_t internal_max_alloc;
|
||||
uint32_t psram_free;
|
||||
uint32_t psram_total;
|
||||
int queue_used;
|
||||
int queue_capacity;
|
||||
};
|
||||
|
||||
inline bool formatCompanionMemoryDiagnostics(
|
||||
char* output, size_t output_size,
|
||||
const CompanionMemoryDiagnostics& diagnostics) {
|
||||
if (output == nullptr || output_size == 0) return false;
|
||||
const int written = snprintf(
|
||||
output, output_size,
|
||||
"Heap free=%lu min=%lu max=%lu int=%lu/%lu PSRAM=%lu/%lu queue=%d/%d",
|
||||
(unsigned long)diagnostics.heap_free,
|
||||
(unsigned long)diagnostics.heap_min,
|
||||
(unsigned long)diagnostics.heap_max_alloc,
|
||||
(unsigned long)diagnostics.internal_free,
|
||||
(unsigned long)diagnostics.internal_max_alloc,
|
||||
(unsigned long)diagnostics.psram_free,
|
||||
(unsigned long)diagnostics.psram_total,
|
||||
diagnostics.queue_used, diagnostics.queue_capacity);
|
||||
return written >= 0 && static_cast<size_t>(written) < output_size;
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
@@ -14,6 +14,7 @@ pio test -e native # all suites except KISS modem
|
||||
pio test -e native_kiss_modem # KISS modem suite
|
||||
pio test -e native -f test_webconfig_keys # a single suite
|
||||
python3 test/test_indicator_display_profile.py # Indicator RAM/scale contract
|
||||
python3 test/test_companion_terminal_profile.py # Companion CLI capability gates
|
||||
```
|
||||
|
||||
A green `[PASSED]` per suite means GoogleTest returned 0 (all assertions
|
||||
@@ -57,6 +58,7 @@ does not reflect the GoogleTest count -- run the built binary directly
|
||||
| `test_remote_cli_reply_cache` | `src/helpers/RemoteCliReplyCache.h`, `src/helpers/RemoteCliRequest.h`, `src/helpers/RemoteCliTimeout.h` | authenticated logical-request matching; bounded recent-reply history; backward-compatible retry identity; 300% response timeout; empty-response completion; on-air truncation and clearing |
|
||||
| `test_companion_frame_queue` | `src/helpers/CompanionFrameQueue.h`, `src/helpers/CompanionHardwareCommandCompat.h` | response/required/best-effort classification; reserved capacity; stable priority; safe eviction; message-waiting coalescing; command `0x42` framed-CLI disambiguation and deprecated hardware-alias mapping |
|
||||
| `test_companion_status_response` | `src/helpers/CompanionStatusResponse.h` | request-tag correlation and minimum status-response length, including rejection of the three-entry ACL payload that previously masqueraded as status |
|
||||
| `test_companion_terminal_diagnostics` | `src/helpers/CompanionTerminalDiagnostics.h` | bounded Full Companion ESP32 heap/PSRAM/offline-queue diagnostic formatting, including truthful no-PSRAM output |
|
||||
| `test_serial_mode_switch` | `src/helpers/ArduinoSerialInterface.cpp`, `src/helpers/MultiSerialInterface.h` | terminal/seeder control-sequence recognition, single-TTY logging transitions, and USB/TCP ownership; queued/atomic USB output under backpressure and short writes; partial-frame busy state; requester-affine replies, locked contact streams, and Bluetooth-only pairing routing |
|
||||
| `test_ble_tx_stall_watchdog` | `src/helpers/BleTxStallWatchdog.h` | exact BLE fragment progress; blocked-reply timeout; rollover-safe elapsed time; disconnect recovery retry and completion |
|
||||
| `test_ble_mota_control` | `src/helpers/BleMotaStream.h`, `CompanionMotaControl.h` | encrypted mOTA channel ring buffering, overflow fail-closed behavior, request gating, and strict rejection of injected or USB-ownership control commands |
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <helpers/CompanionTerminalDiagnostics.h>
|
||||
|
||||
TEST(CompanionTerminalDiagnostics, ReportsEveryEsp32MemoryField) {
|
||||
const mesh::CompanionMemoryDiagnostics diagnostics = {
|
||||
66244, 2516, 63428, 61004, 42812, 6442475, 8386055, 3, 512
|
||||
};
|
||||
char output[160] = {};
|
||||
|
||||
ASSERT_TRUE(mesh::formatCompanionMemoryDiagnostics(
|
||||
output, sizeof(output), diagnostics));
|
||||
EXPECT_STREQ(
|
||||
"Heap free=66244 min=2516 max=63428 int=61004/42812 "
|
||||
"PSRAM=6442475/8386055 queue=3/512",
|
||||
output);
|
||||
}
|
||||
|
||||
TEST(CompanionTerminalDiagnostics, TruthfullyReportsNoPsram) {
|
||||
const mesh::CompanionMemoryDiagnostics diagnostics = {
|
||||
104857, 98234, 90112, 104857, 90112, 0, 0, 0, 16
|
||||
};
|
||||
char output[160] = {};
|
||||
|
||||
ASSERT_TRUE(mesh::formatCompanionMemoryDiagnostics(
|
||||
output, sizeof(output), diagnostics));
|
||||
EXPECT_NE(nullptr, strstr(output, "PSRAM=0/0"));
|
||||
EXPECT_NE(nullptr, strstr(output, "queue=0/16"));
|
||||
}
|
||||
|
||||
TEST(CompanionTerminalDiagnostics, DetectsAReplyBufferThatIsTooSmall) {
|
||||
const mesh::CompanionMemoryDiagnostics diagnostics = {
|
||||
UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX,
|
||||
UINT32_MAX, UINT32_MAX, 512, 512
|
||||
};
|
||||
char output[24] = {};
|
||||
|
||||
EXPECT_FALSE(mesh::formatCompanionMemoryDiagnostics(
|
||||
output, sizeof(output), diagnostics));
|
||||
EXPECT_EQ('\0', output[sizeof(output) - 1]);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
source = (root / "examples/companion_radio/MyMesh.cpp").read_text()
|
||||
features = (root / "examples/companion_radio/CompanionFeatures.h").read_text()
|
||||
|
||||
local_start = source.index("bool MyMesh::handleLocalControlCommand(")
|
||||
local_end = source.index("\n#if COMPANION_FEATURE_TEMP_RADIO\nvoid MyMesh::serviceTempRadio()", local_start)
|
||||
local = source[local_start:local_end]
|
||||
|
||||
assert local.count('strcmp(command, "board") == 0') == 1
|
||||
assert 'board.getManufacturerName()' in local
|
||||
assert re.search(
|
||||
r"#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS\s+"
|
||||
r'if \(strcmp\(command, "memory"\) == 0\).*?'
|
||||
r"formatCompanionMemoryDiagnostics.*?#endif",
|
||||
local,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert "offline_queue_len" in local
|
||||
assert "getOfflineQueueCapacity()" in local
|
||||
|
||||
terminal_start = source.index("void MyMesh::handleTerminalCommand(")
|
||||
terminal_end = source.index("\nvoid MyMesh::enterCLIRescue()", terminal_start)
|
||||
terminal = source[terminal_start:terminal_end]
|
||||
assert 'terminalOutput().print(" board\\r\\n")' in terminal
|
||||
assert re.search(
|
||||
r"#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS\s+"
|
||||
r'terminalOutput\(\)\.print\(" memory\\r\\n"\);\s+#endif',
|
||||
terminal,
|
||||
)
|
||||
|
||||
# This compile-time contract prevents either the implementation or help entry
|
||||
# from leaking onto a non-ESP32 build merely because a recipe set the flag.
|
||||
assert re.search(
|
||||
r"#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS && !defined\(ESP32_PLATFORM\)\s+"
|
||||
r'#error "COMPANION_FEATURE_MEMORY_DIAGNOSTICS requires ESP32"',
|
||||
features,
|
||||
)
|
||||
|
||||
print("test_companion_terminal_profile: PASS")
|
||||
Reference in new issue
Block a user