Fix USB logging ownership, persistence, and reconnect safety
This commit is contained in:
1 parent
c1a533572a
commit
91d533bfff
32 files changed
+1543
-140
No files matched your search
@@ -2607,7 +2607,7 @@ void MyMesh::begin(bool has_display, bool radio_available,
|
||||
}
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
const bool usb_logging_enabled = _prefs.usb_logging_enabled != 0;
|
||||
mesh::setUsbLoggingEnabled(usb_logging_enabled);
|
||||
applyUsbLoggingState(usb_logging_enabled);
|
||||
if (!mesh::saveUsbLoggingBootPreference(usb_logging_enabled)) {
|
||||
MESH_DEBUG_PRINTLN("Unable to save next-boot USB logging interface state");
|
||||
}
|
||||
@@ -3259,7 +3259,7 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
snprintf(reply, reply_size, "Error: use set usb.logging <on|off> [reboot]");
|
||||
} else {
|
||||
_prefs.usb_logging_enabled = enabled ? 1 : 0;
|
||||
mesh::setUsbLoggingEnabled(enabled);
|
||||
applyUsbLoggingState(enabled);
|
||||
if (!savePrefs()) {
|
||||
snprintf(reply, reply_size, "Error: USB logging changed for this boot but save failed");
|
||||
} else if (!mesh::saveUsbLoggingBootPreference(enabled)) {
|
||||
@@ -3777,11 +3777,14 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
_mqtt_enabled = wifi;
|
||||
if (!wifi) stopMQTT();
|
||||
_prefs.usb_logging_enabled = usb ? 1 : 0;
|
||||
mesh::setUsbLoggingEnabled(usb);
|
||||
applyUsbLoggingState(usb);
|
||||
if (!savePrefs() || !mesh::saveUsbLoggingBootPreference(usb)) {
|
||||
snprintf(reply, reply_size, "Error: logging output changed but USB setting could not be saved");
|
||||
} else {
|
||||
snprintf(reply, reply_size, "OK - logging.output %s (saved)", command + 19);
|
||||
// USB ownership changes can clear the terminal's input buffer while
|
||||
// this command is executing. Use parsed values, not that borrowed text.
|
||||
snprintf(reply, reply_size, "OK - logging.output %s (saved)",
|
||||
mesh::cli::loggingOutputName(usb, wifi));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -301,6 +301,7 @@ public:
|
||||
// setup/status commands here; Full builds add TempRadio and OTA commands.
|
||||
bool handleLocalControlCommand(const char* command, char* reply,
|
||||
size_t reply_size);
|
||||
void applyUsbLoggingState(bool enabled);
|
||||
bool handleTxRoutingCommand(const char* command, char* reply, size_t reply_size);
|
||||
|
||||
int getRecentlyHeard(AdvertPath dest[], int max_num);
|
||||
|
||||
@@ -624,6 +624,8 @@ static size_t usb_terminal_line_len = 0;
|
||||
static bool usb_terminal_discard_line = false;
|
||||
static bool usb_host_session_connected = false;
|
||||
static bool usb_logging_terminal_mode = false;
|
||||
static bool usb_logging_network_parked = false;
|
||||
static bool usb_protocol_initialized = false;
|
||||
static mesh::UsbBinaryStartupProbe usb_binary_startup_probe;
|
||||
static mesh::UsbAsciiSessionDefault usb_ascii_session_default;
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
@@ -772,7 +774,10 @@ static void leaveUsbTerminalMode(bool acknowledge) {
|
||||
mesh::discardUsbTerminalOutput();
|
||||
}
|
||||
the_mesh.exitTerminalMode();
|
||||
usb_serial_interface.setPassthroughMode(false);
|
||||
// A network handoff can leave the USB CLI while diagnostics remain enabled.
|
||||
// Do not briefly reopen the framed transport on that same logging stream.
|
||||
usb_serial_interface.setPassthroughMode(
|
||||
!mesh::hasDedicatedUsbLoggingPort() && mesh::isUsbLoggingEnabled());
|
||||
clearUsbTerminalLine();
|
||||
usb_terminal_discard_line = false;
|
||||
usb_logging_terminal_mode = false;
|
||||
@@ -1011,69 +1016,16 @@ static void beginUsbDefaultSession() {
|
||||
serviceUsbAsciiSessionDefault();
|
||||
}
|
||||
|
||||
static void serviceUsbLoggingOwnership(bool logging_enabled);
|
||||
|
||||
static void serviceUsbTerminal() {
|
||||
serviceUsbLoggingOwnership(mesh::isUsbLoggingEnabled());
|
||||
if (usb_logging_network_parked) return;
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
if (usb_mota_mode) {
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
// A single-TTY logger and framed USB mOTA cannot share the primary USB
|
||||
// stream. A remote logging change can arrive while mOTA owns USB, so stop
|
||||
// the transfer before its next binary exchange and return to ASCII.
|
||||
if (!mesh::hasDedicatedUsbLoggingPort()
|
||||
&& mesh::isUsbLoggingEnabled()) {
|
||||
leaveUsbMotaMode(false);
|
||||
enterUsbLoggingTerminalMode();
|
||||
usbTerminalOutput().print(
|
||||
"\r\nUSB mOTA stopped: USB logging owns this port\r\n> ");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
serviceUsbMota();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// A saved logging-on preference makes the one available TTY behave like a
|
||||
// logging repeater: plaintext diagnostics plus an input-capable CLI. Put the
|
||||
// Companion interface into passthrough before it can mix framed traffic with
|
||||
// logs. An active TCP terminal owns the role CLI, so logging must not reclaim
|
||||
// it. Turning logging off retains the ordinary ASCII terminal.
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
const mesh::UsbLoggingTerminalAction logging_action =
|
||||
mesh::selectUsbLoggingTerminalAction(
|
||||
mesh::hasDedicatedUsbLoggingPort(), mesh::isUsbLoggingEnabled(),
|
||||
the_mesh.isTerminalMode(), usb_logging_terminal_mode,
|
||||
true,
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL || defined(WITH_WEBCONFIG)
|
||||
isNetworkTerminalActive()
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
switch (logging_action) {
|
||||
case mesh::UsbLoggingTerminalAction::CLAIM_USB:
|
||||
if (!the_mesh.isTerminalMode()) {
|
||||
enterUsbLoggingTerminalMode();
|
||||
return;
|
||||
}
|
||||
usb_logging_terminal_mode = true;
|
||||
break;
|
||||
case mesh::UsbLoggingTerminalAction::RETURN_TO_BINARY:
|
||||
leaveUsbTerminalMode(true);
|
||||
return;
|
||||
case mesh::UsbLoggingTerminalAction::KEEP_ASCII:
|
||||
// Logging may also be disabled over BLE/WiFi. Stop treating this session
|
||||
// as the logging terminal, but keep the ordinary ASCII terminal active;
|
||||
// do not silently change the USB protocol underneath an idle host. A
|
||||
// remote mode change also cancels any partially typed USB command before
|
||||
// drawing a fresh prompt.
|
||||
usb_logging_terminal_mode = false;
|
||||
clearUsbTerminalLine();
|
||||
usb_terminal_discard_line = false;
|
||||
usbTerminalOutput().print(
|
||||
"\r\nUSB logging off; ASCII terminal active\r\n> ");
|
||||
break;
|
||||
case mesh::UsbLoggingTerminalAction::NO_ACTION:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (!the_mesh.isTerminalMode()) {
|
||||
const mesh::UsbBinaryStartupProbe::Result probe_result =
|
||||
@@ -1200,6 +1152,91 @@ static void serviceUsbTerminal() {
|
||||
}
|
||||
}
|
||||
|
||||
static void serviceUsbLoggingOwnership(bool logging_enabled) {
|
||||
// A saved logging-on preference makes the one available TTY behave like a
|
||||
// logging repeater: plaintext diagnostics plus an input-capable CLI. Put the
|
||||
// Companion interface into passthrough before it can mix framed traffic with
|
||||
// logs. An active TCP terminal owns the role CLI, so logging must not reclaim
|
||||
// it. Turning logging off retains the ordinary ASCII terminal.
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
if (usb_mota_mode) {
|
||||
if (mesh::hasDedicatedUsbLoggingPort() || !logging_enabled) return;
|
||||
// Run before framed dispatch, not just the later terminal-input service.
|
||||
leaveUsbMotaMode(false);
|
||||
}
|
||||
#endif
|
||||
const mesh::UsbLoggingTerminalAction logging_action =
|
||||
mesh::selectUsbLoggingTerminalAction(
|
||||
mesh::hasDedicatedUsbLoggingPort(), logging_enabled,
|
||||
the_mesh.isTerminalMode(), usb_logging_terminal_mode,
|
||||
true,
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL || defined(WITH_WEBCONFIG)
|
||||
isNetworkTerminalActive()
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
if (usb_logging_network_parked
|
||||
&& logging_action != mesh::UsbLoggingTerminalAction::PARK_USB_FOR_NETWORK) {
|
||||
// Bytes typed while USB was log-only belong to neither the new ASCII CLI
|
||||
// nor a new Binary client. Bound the discard to the existing RX snapshot.
|
||||
Stream& input = mesh::usbCompanionPort();
|
||||
int pending = input.available();
|
||||
while (pending-- > 0) input.read();
|
||||
usb_logging_network_parked = false;
|
||||
usb_serial_interface.setPassthroughMode(the_mesh.isTerminalMode());
|
||||
}
|
||||
switch (logging_action) {
|
||||
case mesh::UsbLoggingTerminalAction::PARK_USB_FOR_NETWORK:
|
||||
if (!usb_logging_network_parked
|
||||
|| !usb_serial_interface.isPassthroughMode()) {
|
||||
usb_binary_startup_probe.cancel();
|
||||
cancelUsbSerialOperations();
|
||||
mesh::discardUsbTerminalOutput();
|
||||
usb_serial_interface.setPassthroughMode(true);
|
||||
clearUsbTerminalLine();
|
||||
usb_terminal_discard_line = false;
|
||||
}
|
||||
usb_logging_network_parked = true;
|
||||
return; // Do not move the role CLI away from TCP/browser.
|
||||
case mesh::UsbLoggingTerminalAction::CLAIM_USB:
|
||||
usb_logging_network_parked = false;
|
||||
if (!the_mesh.isTerminalMode()) {
|
||||
enterUsbLoggingTerminalMode();
|
||||
return;
|
||||
}
|
||||
usb_logging_terminal_mode = true;
|
||||
break;
|
||||
case mesh::UsbLoggingTerminalAction::RETURN_TO_BINARY:
|
||||
leaveUsbTerminalMode(true);
|
||||
return;
|
||||
case mesh::UsbLoggingTerminalAction::KEEP_ASCII:
|
||||
// Logging may also be disabled over BLE/WiFi. Stop treating this session
|
||||
// as the logging terminal, but keep the ordinary ASCII terminal active;
|
||||
// do not silently change the USB protocol underneath an idle host. A
|
||||
// remote mode change also cancels any partially typed USB command before
|
||||
// drawing a fresh prompt.
|
||||
usb_logging_terminal_mode = false;
|
||||
clearUsbTerminalLine();
|
||||
usb_terminal_discard_line = false;
|
||||
usbTerminalOutput().print(
|
||||
"\r\nUSB logging off; ASCII terminal active\r\n> ");
|
||||
break;
|
||||
case mesh::UsbLoggingTerminalAction::NO_ACTION:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::applyUsbLoggingState(bool enabled) {
|
||||
// Park the framed transport before opening the diagnostic gate, including
|
||||
// TCP/browser commands which execute outside the mesh dispatcher.
|
||||
if (enabled && usb_protocol_initialized) serviceUsbLoggingOwnership(true);
|
||||
mesh::setUsbLoggingEnabled(enabled);
|
||||
if (usb_protocol_initialized) serviceUsbLoggingOwnership(enabled);
|
||||
}
|
||||
|
||||
static void expireUsbBinaryStartupProbeBeforeDispatch() {
|
||||
const uint32_t now = millis();
|
||||
if (!usb_binary_startup_probe.hasTimedOut(now)) return;
|
||||
@@ -1216,6 +1253,12 @@ static void expireUsbBinaryStartupProbeBeforeDispatch() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(ENABLE_USB_INTERFACE)
|
||||
void MyMesh::applyUsbLoggingState(bool enabled) {
|
||||
mesh::setUsbLoggingEnabled(enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_USB_INTERFACE) && (COMPANION_FEATURE_NETWORK_TERMINAL || defined(WITH_WEBCONFIG))
|
||||
static bool isNetworkTerminalActive() {
|
||||
return the_mesh.isAnyNetworkTerminalMode();
|
||||
@@ -1237,7 +1280,10 @@ bool MyMesh::beginStreamTerminal(Stream& output) {
|
||||
hasObservableActiveUsbTerminalClient(), input_idle,
|
||||
usb_serial_interface.getCompletedFrameCount())) return false;
|
||||
if (ascii_selected) leaveUsbTerminalMode(false);
|
||||
if (enterNetworkTerminalMode(output)) return true;
|
||||
if (enterNetworkTerminalMode(output)) {
|
||||
serviceUsbLoggingOwnership(mesh::isUsbLoggingEnabled());
|
||||
return true;
|
||||
}
|
||||
if (browser_usb_handoff.shouldRestoreAscii(
|
||||
usb_serial_interface.getCompletedFrameCount())) enterUsbTerminalMode();
|
||||
return false;
|
||||
@@ -1937,6 +1983,9 @@ void halt() {
|
||||
"ERROR: another client currently owns the Full Companion terminal\r\n");
|
||||
ota_console_client.stop();
|
||||
}
|
||||
#if defined(ENABLE_USB_INTERFACE)
|
||||
serviceUsbLoggingOwnership(mesh::isUsbLoggingEnabled());
|
||||
#endif
|
||||
#else
|
||||
ota_console_client.print("Local CLI and OTA console - type `version` or `ota status`\r\n> ");
|
||||
#endif
|
||||
@@ -3248,6 +3297,7 @@ void setup() {
|
||||
usb_serial_interface.setConnectedCheck([]() { return (bool)Serial; });
|
||||
#endif
|
||||
interface_manager.addInterface(InterfaceType::USB, &usb_serial_interface);
|
||||
usb_protocol_initialized = true;
|
||||
// Select ASCII before any framed dispatch, including non-Full USB builds.
|
||||
// BLE/WiFi/Ethernet keep their existing Binary Companion transports.
|
||||
beginUsbDefaultSession();
|
||||
@@ -3317,6 +3367,7 @@ void loop() {
|
||||
#if defined(ENABLE_USB_INTERFACE)
|
||||
serviceUsbTerminalHostSessionReset();
|
||||
serviceUsbAsciiSessionDefault();
|
||||
serviceUsbLoggingOwnership(mesh::isUsbLoggingEnabled()); // Before framed dispatch.
|
||||
#endif
|
||||
// Identify nRF52 CDC 1 when a terminal opens it. Doing this on the connection
|
||||
// edge avoids losing the marker before the host has opened the port.
|
||||
|
||||
@@ -5092,9 +5092,34 @@ void MyMesh::dumpLogFile() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::printAclSerial() {
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (hasPendingSerialOutput()) {
|
||||
mesh::usbConsolePort().printf("Err - USB output busy\r\n");
|
||||
return;
|
||||
}
|
||||
// Reuse the bounded file-output buffer. Command admission prevents overlapping
|
||||
// USB jobs, and session reset cancels the retained row before a new host reads.
|
||||
serial_acl_next = 0;
|
||||
serial_acl_count = acl.getNumClients();
|
||||
serial_acl_header = true;
|
||||
serial_log_pending_size = 0;
|
||||
#else
|
||||
mesh::usbConsolePort().printf("ACL:\r\n");
|
||||
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||
auto c = acl.getClientByIdx(i);
|
||||
if (c->permissions == 0) continue;
|
||||
char public_key[PUB_KEY_SIZE * 2 + 1];
|
||||
mesh::Utils::toHex(public_key, c->id.pub_key, PUB_KEY_SIZE);
|
||||
mesh::usbConsolePort().printf("%02X %s\n", c->permissions, public_key);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
bool MyMesh::hasPendingSerialOutput() const {
|
||||
return serial_log_active || serial_log_eof_pending || serial_recent_next >= 0;
|
||||
return serial_log_active || serial_log_eof_pending || serial_recent_next >= 0
|
||||
|| serial_acl_next >= 0;
|
||||
}
|
||||
|
||||
void MyMesh::cancelPendingSerialOutput() {
|
||||
@@ -5104,6 +5129,9 @@ void MyMesh::cancelPendingSerialOutput() {
|
||||
serial_log_skip_line = false;
|
||||
serial_log_remaining = 0;
|
||||
serial_log_pending_size = 0;
|
||||
serial_acl_next = -1;
|
||||
serial_acl_count = 0;
|
||||
serial_acl_header = false;
|
||||
serial_recent_next = -1;
|
||||
serial_recent_count = 0;
|
||||
serial_recent_header = false;
|
||||
@@ -5114,6 +5142,43 @@ void MyMesh::cancelPendingSerialOutput() {
|
||||
void MyMesh::servicePendingSerialOutput() {
|
||||
Stream& console = mesh::usbConsolePort();
|
||||
|
||||
if (serial_acl_next >= 0) {
|
||||
if (serial_log_pending_size == 0) {
|
||||
if (serial_acl_header) {
|
||||
static const char header[] = "ACL:\r\n";
|
||||
memcpy(serial_log_pending, header, sizeof(header) - 1);
|
||||
serial_log_pending_size = sizeof(header) - 1;
|
||||
serial_acl_header = false;
|
||||
} else {
|
||||
// At most one row per pass; deleted entries can be skipped without
|
||||
// allocating a table-sized response or waiting for USB FIFO space.
|
||||
while (serial_acl_next < serial_acl_count
|
||||
&& serial_acl_next < acl.getNumClients()) {
|
||||
auto* client = acl.getClientByIdx(serial_acl_next++);
|
||||
if (!client->permissions) continue;
|
||||
char key[PUB_KEY_SIZE * 2 + 1];
|
||||
mesh::Utils::toHex(key, client->id.pub_key, PUB_KEY_SIZE);
|
||||
serial_log_pending_size = snprintf(serial_log_pending,
|
||||
sizeof(serial_log_pending), "%02X %s\n", client->permissions, key);
|
||||
break;
|
||||
}
|
||||
if (serial_log_pending_size == 0) {
|
||||
serial_acl_next = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (console.availableForWrite() < static_cast<int>(serial_log_pending_size)) return;
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), serial_log_pending_size);
|
||||
if (written > serial_log_pending_size) written = serial_log_pending_size;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (serial_recent_next >= 0) {
|
||||
char record[64];
|
||||
int length = 0;
|
||||
@@ -12093,17 +12158,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mesh::usbConsolePort().printf("ACL:\r\n");
|
||||
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||
auto c = acl.getClientByIdx(i);
|
||||
if (c->permissions == 0) continue; // skip deleted (or guest) entries
|
||||
|
||||
// Admit each line together so concurrent USB diagnostics cannot split
|
||||
// a public key or insert text between its permission prefix and value.
|
||||
char public_key[PUB_KEY_SIZE * 2 + 1];
|
||||
mesh::Utils::toHex(public_key, c->id.pub_key, PUB_KEY_SIZE);
|
||||
mesh::usbConsolePort().printf("%02X %s\n", c->permissions, public_key);
|
||||
}
|
||||
printAclSerial();
|
||||
reply[0] = 0;
|
||||
} else if (handleClientPathCommand(sender, command, reply)) {
|
||||
return;
|
||||
|
||||
@@ -322,6 +322,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
bool serial_log_active = false;
|
||||
bool serial_log_eof_pending = false;
|
||||
bool serial_log_skip_line = false;
|
||||
int serial_acl_next = -1;
|
||||
int serial_acl_count = 0;
|
||||
bool serial_acl_header = false;
|
||||
int serial_recent_next = -1;
|
||||
int serial_recent_count = 0;
|
||||
bool serial_recent_header = false;
|
||||
@@ -1073,6 +1076,7 @@ public:
|
||||
}
|
||||
|
||||
void dumpLogFile() override;
|
||||
void printAclSerial();
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
// Large local-only replies advance between radio service passes.
|
||||
bool hasPendingSerialOutput() const;
|
||||
|
||||
@@ -1669,9 +1669,33 @@ void MyMesh::dumpLogFile() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::printAclSerial() {
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (hasPendingSerialOutput()) {
|
||||
mesh::usbConsolePort().printf("Err - USB output busy\r\n");
|
||||
return;
|
||||
}
|
||||
// Reuse the bounded file-output buffer. Command admission prevents overlapping
|
||||
// USB jobs, and session reset cancels the retained row before a new host reads.
|
||||
serial_acl_next = 0;
|
||||
serial_acl_count = acl.getNumClients();
|
||||
serial_acl_header = true;
|
||||
serial_log_pending_size = 0;
|
||||
#else
|
||||
mesh::usbConsolePort().printf("ACL:\r\n");
|
||||
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||
auto c = acl.getClientByIdx(i);
|
||||
if (c->permissions == 0) continue;
|
||||
char public_key[PUB_KEY_SIZE * 2 + 1];
|
||||
mesh::Utils::toHex(public_key, c->id.pub_key, PUB_KEY_SIZE);
|
||||
mesh::usbConsolePort().printf("%02X %s\n", c->permissions, public_key);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
bool MyMesh::hasPendingSerialOutput() const {
|
||||
return serial_log_active || serial_log_eof_pending;
|
||||
return serial_log_active || serial_log_eof_pending || serial_acl_next >= 0;
|
||||
}
|
||||
|
||||
void MyMesh::cancelPendingSerialOutput() {
|
||||
@@ -1681,10 +1705,49 @@ void MyMesh::cancelPendingSerialOutput() {
|
||||
serial_log_skip_line = false;
|
||||
serial_log_remaining = 0;
|
||||
serial_log_pending_size = 0;
|
||||
serial_acl_next = -1;
|
||||
serial_acl_count = 0;
|
||||
serial_acl_header = false;
|
||||
}
|
||||
|
||||
void MyMesh::servicePendingSerialOutput() {
|
||||
Stream& console = mesh::usbConsolePort();
|
||||
if (serial_acl_next >= 0) {
|
||||
if (serial_log_pending_size == 0) {
|
||||
if (serial_acl_header) {
|
||||
static const char header[] = "ACL:\r\n";
|
||||
memcpy(serial_log_pending, header, sizeof(header) - 1);
|
||||
serial_log_pending_size = sizeof(header) - 1;
|
||||
serial_acl_header = false;
|
||||
} else {
|
||||
// At most one row per pass; deleted entries can be skipped without
|
||||
// allocating a table-sized response or waiting for USB FIFO space.
|
||||
while (serial_acl_next < serial_acl_count
|
||||
&& serial_acl_next < acl.getNumClients()) {
|
||||
auto* client = acl.getClientByIdx(serial_acl_next++);
|
||||
if (!client->permissions) continue;
|
||||
char key[PUB_KEY_SIZE * 2 + 1];
|
||||
mesh::Utils::toHex(key, client->id.pub_key, PUB_KEY_SIZE);
|
||||
serial_log_pending_size = snprintf(serial_log_pending,
|
||||
sizeof(serial_log_pending), "%02X %s\n", client->permissions, key);
|
||||
break;
|
||||
}
|
||||
if (serial_log_pending_size == 0) {
|
||||
serial_acl_next = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (console.availableForWrite() < static_cast<int>(serial_log_pending_size)) return;
|
||||
size_t written = console.write(
|
||||
reinterpret_cast<const uint8_t*>(serial_log_pending), serial_log_pending_size);
|
||||
if (written > serial_log_pending_size) written = serial_log_pending_size;
|
||||
serial_log_pending_size -= written;
|
||||
if (written > 0 && serial_log_pending_size > 0) {
|
||||
memmove(serial_log_pending, serial_log_pending + written, serial_log_pending_size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!serial_log_active) {
|
||||
// CommonCLI's synchronous EOF is suppressed until the queued dump ends.
|
||||
static const char eof[] = " -> EOF\r\n";
|
||||
@@ -2440,17 +2503,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mesh::usbConsolePort().printf("ACL:\r\n");
|
||||
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||
auto c = acl.getClientByIdx(i);
|
||||
if (c->permissions == 0) continue; // skip deleted (or guest) entries
|
||||
|
||||
// Admit each line together so concurrent USB diagnostics cannot split
|
||||
// a public key or insert text between its permission prefix and value.
|
||||
char public_key[PUB_KEY_SIZE * 2 + 1];
|
||||
mesh::Utils::toHex(public_key, c->id.pub_key, PUB_KEY_SIZE);
|
||||
mesh::usbConsolePort().printf("%02X %s\n", c->permissions, public_key);
|
||||
}
|
||||
printAclSerial();
|
||||
reply[0] = 0;
|
||||
#if defined(WITH_MQTT_NEIGHBORS)
|
||||
} else if (memcmp(command, "discover.neighbors", 18) == 0) {
|
||||
|
||||
@@ -185,6 +185,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks,
|
||||
bool serial_log_active = false;
|
||||
bool serial_log_eof_pending = false;
|
||||
bool serial_log_skip_line = false;
|
||||
int serial_acl_next = -1;
|
||||
int serial_acl_count = 0;
|
||||
bool serial_acl_header = false;
|
||||
#endif
|
||||
uint32_t last_millis;
|
||||
uint64_t uptime_millis;
|
||||
@@ -488,6 +491,7 @@ public:
|
||||
}
|
||||
|
||||
void dumpLogFile() override;
|
||||
void printAclSerial();
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
// Large local-only replies advance between radio service passes.
|
||||
bool hasPendingSerialOutput() const;
|
||||
|
||||
Reference in new issue
Block a user