Quiet USB packet logs for five minutes after console commands
This commit is contained in:
1 parent
3ec3a07236
commit
b3df8bfd3f
23 files changed
+347
-31
No files matched your search
@@ -1046,6 +1046,7 @@ jobs:
|
||||
python3 -B test/test_usb_logging_watchdog.py
|
||||
python3 -B test/test_usb_watchdog_event.py
|
||||
python3 -B test/test_usb_logging_client_activity.py
|
||||
python3 -B test/test_usb_packet_logging_quiet.py
|
||||
python3 -B test/test_usb_logging_watchdog_runtime.py
|
||||
python3 -B test/test_usb_logging_hwcdc_watchdog.py -v
|
||||
python3 -B test/test_usb_logging_hwcdc_presence.py -v
|
||||
|
||||
@@ -1694,6 +1694,26 @@ loading older preferences without a debug field. Updates retain an existing
|
||||
saved `usb.logging` choice. Older binaries without the new `usb.debug`
|
||||
command retain their previous behavior until upgraded.
|
||||
|
||||
On a shared USB console, an interactive text command pauses `RAW`, `RX`, and
|
||||
`TX` packet log lines for five minutes. Each command restarts the timer;
|
||||
logging then resumes automatically if its saved master is on. `get usb.logging`
|
||||
still reports that saved setting. `set usb.logging on` or
|
||||
`set logging.output usb|both` resumes packet logs immediately after a successful
|
||||
save. No reboot is needed.
|
||||
|
||||
The routine `stats-core`, `stats-radio`, and `stats-packets` polls do not start
|
||||
or extend this pause. A separate logging USB port and explicit Companion
|
||||
`usb.logging stream` mode also keep logging. The pause leaves command replies,
|
||||
noise-floor sampling, saved history, LoRa telemetry, and direct WiFi MQTT
|
||||
running. A collector reading USB packet logs misses those log copies during
|
||||
the pause, including telemetry packets; this is not buffered capture.
|
||||
|
||||
`meshcoretomqtt` polls these three stats commands every 300 seconds and
|
||||
publishes `noise_floor` from the `stats-radio` reply in its MQTT status. These
|
||||
polls keep working during the pause. Its startup identity queries do trigger
|
||||
a five-minute packet-capture gap on a shared console, as do ordinary commands
|
||||
sent through its remote serial feature.
|
||||
|
||||
For packet-only capture, use these separate text commands:
|
||||
|
||||
```text
|
||||
|
||||
@@ -46,6 +46,14 @@ LoRa, management reports, and ordinary framed Companion traffic do not.
|
||||
No new heartbeat command is required. A cable, open TTY, DTR, or kernel USB
|
||||
ACK alone cannot qualify Auto.
|
||||
|
||||
Interactive text commands on a shared USB console pause packet logs for five
|
||||
minutes after the last command. The three routine stats polls above keep
|
||||
their replies and renew the lease without starting or extending this pause.
|
||||
Dedicated logging ports and explicit Companion stream mode keep logging.
|
||||
The pause does not disable measurements, LoRa telemetry, direct WiFi MQTT,
|
||||
or the saved logging master. USB packet collectors miss log copies during
|
||||
the pause. See [live USB logging](cli_commands.md#control-live-usb-logging).
|
||||
|
||||
`logger=1` requires the USB logging master, an observed host and reader, a fresh
|
||||
stats lease, and no physical TX stall. Any program polling those commands can
|
||||
qualify: this is not application identification or proof that packets were
|
||||
|
||||
@@ -820,7 +820,7 @@ const char* MyMesh::getLogDateTime() {
|
||||
|
||||
void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
if (mesh::isUsbLoggingEnabled()) {
|
||||
if (mesh::isUsbPacketLoggingAllowed()) {
|
||||
mesh::SerialLogLine<> line;
|
||||
#if MESH_PACKET_LOGGING_COMPACT
|
||||
line.printf("R");
|
||||
@@ -4380,22 +4380,25 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
mesh::cli::formatError(reply, reply_size, "USB logging changed for this boot but save failed");
|
||||
} else if (!mesh::saveUsbLoggingBootPreference(enabled)) {
|
||||
mesh::cli::formatError(reply, reply_size, "setting saved, but next-boot USB interface state could not be saved");
|
||||
} else if (mesh::usbLoggingInterfaceRestartRequired()
|
||||
|| (enabled && packet_stream != mesh::isUsbLoggingPacketStream())) {
|
||||
if (reboot_if_needed) {
|
||||
snprintf(reply, reply_size,
|
||||
"OK - USB logging %s (saved); rebooting to change USB interfaces",
|
||||
enabled ? (packet_stream ? "stream" : "on") : "off");
|
||||
_scheduled_reboot_at = futureMillis(1000);
|
||||
if (_scheduled_reboot_at == 0) _scheduled_reboot_at = 1;
|
||||
} else {
|
||||
snprintf(reply, reply_size,
|
||||
"OK - USB logging %s (saved); reboot required to change USB interfaces",
|
||||
enabled ? (packet_stream ? "stream" : "on") : "off");
|
||||
}
|
||||
} else {
|
||||
snprintf(reply, reply_size, "OK - USB logging %s (saved)",
|
||||
enabled ? (packet_stream ? "stream" : "on") : "off");
|
||||
if (enabled) mesh::resumeUsbPacketLogging();
|
||||
if (mesh::usbLoggingInterfaceRestartRequired()
|
||||
|| (enabled && packet_stream != mesh::isUsbLoggingPacketStream())) {
|
||||
if (reboot_if_needed) {
|
||||
snprintf(reply, reply_size,
|
||||
"OK - USB logging %s (saved); rebooting to change USB interfaces",
|
||||
enabled ? (packet_stream ? "stream" : "on") : "off");
|
||||
_scheduled_reboot_at = futureMillis(1000);
|
||||
if (_scheduled_reboot_at == 0) _scheduled_reboot_at = 1;
|
||||
} else {
|
||||
snprintf(reply, reply_size,
|
||||
"OK - USB logging %s (saved); reboot required to change USB interfaces",
|
||||
enabled ? (packet_stream ? "stream" : "on") : "off");
|
||||
}
|
||||
} else {
|
||||
snprintf(reply, reply_size, "OK - USB logging %s (saved)",
|
||||
enabled ? (packet_stream ? "stream" : "on") : "off");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -4931,6 +4934,7 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
if (!savePrefs() || !mesh::saveUsbLoggingBootPreference(usb)) {
|
||||
mesh::cli::formatError(reply, reply_size, "logging output changed but USB setting could not be saved");
|
||||
} else {
|
||||
if (usb) mesh::resumeUsbPacketLogging();
|
||||
// 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)",
|
||||
@@ -11403,6 +11407,7 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
|
||||
complete_line:
|
||||
{
|
||||
if (strlen(cli_command) == len) mesh::noteUsbConsoleCommand(cli_command);
|
||||
reply_buf[0] = 0;
|
||||
#if COMPANION_FEATURE_READER
|
||||
if (mesh::morseTimingActive(_radio) && isCompanionReaderRequest(cli_command)) {
|
||||
|
||||
@@ -1408,6 +1408,7 @@ static void serviceUsbTerminal() {
|
||||
#endif
|
||||
// NUL-containing lines were discarded before reaching this point. On
|
||||
// dual CDC hardware only CDC1 is the logging-reader endpoint.
|
||||
mesh::noteUsbConsoleCommand(usb_terminal_line);
|
||||
if (!mesh::hasDedicatedUsbLoggingPort())
|
||||
mesh::noteUsbLoggingStatsCommand(usb_terminal_line);
|
||||
the_mesh.handleTerminalCommand(usb_terminal_line);
|
||||
|
||||
@@ -1261,7 +1261,7 @@ bool MyMesh::allowTransportPacket(const mesh::Packet* packet, uint8_t context) {
|
||||
|
||||
void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
if (mesh::isUsbLoggingEnabled()) {
|
||||
if (mesh::isUsbPacketLoggingAllowed()) {
|
||||
#if MESH_PACKET_LOGGING_COMPACT
|
||||
mesh::serialLogCompactPacket(mesh::usbLoggingPort(), 'R', raw, len);
|
||||
#else
|
||||
|
||||
@@ -626,8 +626,10 @@ static void __attribute__((noinline)) serviceCommandInterfaces() {
|
||||
console.print('\n');
|
||||
char reply[160];
|
||||
reply[0] = 0;
|
||||
if (strlen(command) == static_cast<size_t>(len))
|
||||
if (strlen(command) == static_cast<size_t>(len)) {
|
||||
mesh::noteUsbConsoleCommand(command);
|
||||
mesh::noteUsbLoggingStatsCommand(command);
|
||||
}
|
||||
#if defined(MESH_SOAK_DIAGNOSTICS)
|
||||
if (mesh::hil::handleSoakCommand(command, reply, sizeof(reply))) {
|
||||
console.print(" -> ");
|
||||
|
||||
@@ -1458,7 +1458,7 @@ bool MyMesh::allowTransportPacket(const mesh::Packet* packet, uint8_t context) {
|
||||
|
||||
void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
if (mesh::isUsbLoggingEnabled()) {
|
||||
if (mesh::isUsbPacketLoggingAllowed()) {
|
||||
mesh::SerialLogLine<> line;
|
||||
line.printf("%s RAW: ", getLogDateTime());
|
||||
line.hex(raw, len);
|
||||
|
||||
@@ -413,8 +413,10 @@ void loop() {
|
||||
char reply[160];
|
||||
reply[0] = 0;
|
||||
if (len < static_cast<int>(sizeof(command) - 1)
|
||||
&& strlen(command) == static_cast<size_t>(len - 1))
|
||||
&& strlen(command) == static_cast<size_t>(len - 1)) {
|
||||
mesh::noteUsbConsoleCommand(command);
|
||||
mesh::noteUsbLoggingStatsCommand(command);
|
||||
}
|
||||
#ifdef ETHERNET_ENABLED
|
||||
if (!ethernet_handle_command(command, reply)) {
|
||||
the_mesh.handleUsbCommand(command, reply);
|
||||
|
||||
@@ -396,8 +396,10 @@ void loop() {
|
||||
char reply[160];
|
||||
reply[0] = 0;
|
||||
if (len < static_cast<int>(sizeof(command) - 1)
|
||||
&& strlen(command) == static_cast<size_t>(len - 1))
|
||||
&& strlen(command) == static_cast<size_t>(len - 1)) {
|
||||
mesh::noteUsbConsoleCommand(command);
|
||||
mesh::noteUsbLoggingStatsCommand(command);
|
||||
}
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
if (reply[0]) {
|
||||
console.print(" -> ");
|
||||
|
||||
+2
-2
@@ -234,7 +234,7 @@ bool Dispatcher::startOutboundTransmit() {
|
||||
outbound_expiry = futureMillis(max_airtime);
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
if (isUsbLoggingEnabled()) {
|
||||
if (isUsbPacketLoggingAllowed()) {
|
||||
#if MESH_PACKET_LOGGING_COMPACT
|
||||
serialLogCompactPacket(usbLoggingPort(), 'T', raw, len);
|
||||
#else
|
||||
@@ -1164,7 +1164,7 @@ void Dispatcher::checkRecv() {
|
||||
}
|
||||
if (pkt) {
|
||||
#if MESH_PACKET_LOGGING && !MESH_PACKET_LOGGING_COMPACT
|
||||
if (isUsbLoggingEnabled()) {
|
||||
if (isUsbPacketLoggingAllowed()) {
|
||||
logPacketLine("RX", pkt, pkt->getRawLength(), true, score, air_time);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -4453,6 +4453,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
}
|
||||
// Commit before changing the stream's live protocol ownership.
|
||||
mesh::setUsbLoggingEnabled(enabled);
|
||||
if (enabled) mesh::resumeUsbPacketLogging();
|
||||
snprintf(reply, 160, "OK - USB logging %s (saved)", enabled ? "on" : "off");
|
||||
} else {
|
||||
mesh::cli::formatError(reply, 160, "usage set usb.logging on|off");
|
||||
@@ -4503,6 +4504,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
return;
|
||||
}
|
||||
mesh::setUsbLoggingEnabled(usb_enabled);
|
||||
if (usb_enabled) mesh::resumeUsbPacketLogging();
|
||||
const bool applied = wifi_enabled ? _callbacks->setMqttBridgeState(true)
|
||||
: _callbacks->requestMqttBridgeStop();
|
||||
if (!applied) {
|
||||
|
||||
@@ -94,6 +94,9 @@ static std::atomic<bool> usb_logging_enabled{true};
|
||||
#endif
|
||||
static std::atomic<bool> usb_logging_preference_known{false};
|
||||
static std::atomic<bool> usb_debug_enabled{false};
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
static std::atomic<uint32_t> usb_packet_logging_quiet_until{0};
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
static std::atomic<bool> usb_binary_stream_owned{false};
|
||||
static std::atomic<uint32_t> usb_text_writers{0};
|
||||
@@ -1294,6 +1297,36 @@ bool isUsbLoggingEnabled() {
|
||||
return usb_logging_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
void noteUsbConsoleCommand(const char* command) {
|
||||
if (command == nullptr || hasDedicatedUsbLoggingPort() || isUsbLoggingPacketStream()) return;
|
||||
while (*command == ' ' || *command == '\t' || *command == '\r' || *command == '\n') ++command;
|
||||
if (*command == 0 || isUsbLoggingStatsCommand(command)) return;
|
||||
for (const unsigned char* p = reinterpret_cast<const unsigned char*>(command); *p; ++p) {
|
||||
if ((*p < 32 && *p != '\t') || *p >= 127) return;
|
||||
}
|
||||
uint32_t deadline = uint32_t(millis()) + 300000UL;
|
||||
if (deadline == 0) deadline = 1;
|
||||
usb_packet_logging_quiet_until.store(deadline, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void resumeUsbPacketLogging() {
|
||||
usb_packet_logging_quiet_until.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool isUsbPacketLoggingAllowed() {
|
||||
uint32_t deadline = usb_packet_logging_quiet_until.load(std::memory_order_relaxed);
|
||||
bool quiet = deadline != 0;
|
||||
if (quiet && int32_t(uint32_t(millis()) - deadline) >= 0) {
|
||||
// Do not clear a newer command's pause if it raced this expiry sample.
|
||||
quiet = !usb_packet_logging_quiet_until.compare_exchange_strong(
|
||||
deadline, 0, std::memory_order_relaxed) && deadline != 0;
|
||||
}
|
||||
return isUsbLoggingEnabled()
|
||||
&& (hasDedicatedUsbLoggingPort() || isUsbLoggingPacketStream() || !quiet);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isUsbDebugEnabled() {
|
||||
return usb_debug_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
@@ -92,6 +92,19 @@ inline size_t nextUsbLoggingIdentityOffset(size_t current_offset,
|
||||
// protect framed traffic and restores its saved choice after preferences load.
|
||||
bool isUsbLoggingEnabled();
|
||||
void setUsbLoggingEnabled(bool enabled);
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
// Human console commands pause packet records for five minutes without
|
||||
// changing the saved logging setting, functional replies, or debug output.
|
||||
// Dedicated logging endpoints, explicit packet streams, and stats polls keep
|
||||
// their continuous capture contract. Explicit logging-on commands may resume.
|
||||
void noteUsbConsoleCommand(const char* command);
|
||||
void resumeUsbPacketLogging();
|
||||
bool isUsbPacketLoggingAllowed();
|
||||
#else
|
||||
inline void noteUsbConsoleCommand(const char*) {}
|
||||
inline void resumeUsbPacketLogging() {}
|
||||
inline bool isUsbPacketLoggingAllowed() { return false; }
|
||||
#endif
|
||||
// Verbose diagnostics are opt-in and persisted by the role preferences.
|
||||
// The requested setting survives switching the master USB output gate off.
|
||||
bool isUsbDebugEnabled();
|
||||
|
||||
@@ -30,6 +30,7 @@ struct Stream {
|
||||
namespace mesh {
|
||||
bool morseTimingActive(const void*) { return morse_active; }
|
||||
bool saveUsbLoggingBootPreference(bool) { return boot_save_ok; }
|
||||
void resumeUsbPacketLogging() {}
|
||||
bool usbLoggingInterfaceRestartRequired() { return true; }
|
||||
bool isUsbLoggingPacketStream() { return false; }
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ HARNESS = r'''
|
||||
struct Prefs { uint8_t usb_logging_enabled = 0, bridge_enabled = 0; };
|
||||
static bool live_usb = false;
|
||||
static unsigned usb_calls = 0;
|
||||
static unsigned resume_calls = 0;
|
||||
namespace mesh {
|
||||
void setUsbLoggingEnabled(bool value) { live_usb = value; ++usb_calls; }
|
||||
void resumeUsbPacketLogging() { ++resume_calls; }
|
||||
}
|
||||
struct Callbacks {
|
||||
bool live_wifi = false, apply_ok = true, stopping = false, pending = false;
|
||||
@@ -50,6 +52,7 @@ static void check(const char* command, bool usb, bool wifi, bool failed) {
|
||||
live_usb = !usb;
|
||||
cli.callbacks.live_wifi = !wifi;
|
||||
usb_calls = 0;
|
||||
resume_calls = 0;
|
||||
cli.save_ok = !failed;
|
||||
char reply[160]{};
|
||||
cli.set(command, reply);
|
||||
@@ -58,6 +61,7 @@ static void check(const char* command, bool usb, bool wifi, bool failed) {
|
||||
assert(cli.persisted.usb_logging_enabled == (failed ? !usb : usb));
|
||||
assert(live_usb == (failed ? !usb : usb));
|
||||
assert(usb_calls == (failed ? 0u : 1u));
|
||||
assert(resume_calls == (!failed && usb ? 1u : 0u));
|
||||
assert(strstr(reply, failed ? "unconfirmed" : "(saved)"));
|
||||
if (failed) assert(strstr(reply, "reboot may differ"));
|
||||
if (strncmp(command, "logging.output", 14) == 0) {
|
||||
@@ -88,9 +92,11 @@ int main() {
|
||||
cli.set("logging.output invalid", reply);
|
||||
assert(cli.saves == 0 && strstr(reply, "Error:"));
|
||||
cli.callbacks.apply_ok = false;
|
||||
resume_calls = 0;
|
||||
cli.set("logging.output both", reply);
|
||||
assert(cli.persisted.usb_logging_enabled && cli.persisted.bridge_enabled);
|
||||
assert(live_usb && !cli.callbacks.live_wifi);
|
||||
assert(resume_calls == 1);
|
||||
assert(strstr(reply, "saved, but MQTT runtime change failed"));
|
||||
// A restart during cleanup saves intent without interrupting the old worker.
|
||||
cli.callbacks.stopping = true;
|
||||
@@ -120,7 +126,10 @@ class LoggingTransactionTest(unittest.TestCase):
|
||||
#include "helpers/CLICommandUtils.h"
|
||||
static char input[64];
|
||||
namespace CompanionMqttSetupPortal { static bool saveEnabled(bool) { return true; } }
|
||||
namespace mesh { static bool saveUsbLoggingBootPreference(bool) { return true; } }
|
||||
namespace mesh {
|
||||
static bool saveUsbLoggingBootPreference(bool) { return true; }
|
||||
inline void resumeUsbPacketLogging() {}
|
||||
}
|
||||
struct Mesh {
|
||||
bool _mqtt_enabled = true;
|
||||
struct { unsigned usb_logging_enabled = 1; } _prefs;
|
||||
|
||||
@@ -68,6 +68,7 @@ void setUsbLoggingEnabled(bool enabled) {
|
||||
}
|
||||
void setUsbDebugEnabled(bool enabled) { usb_debug = enabled; ++debug_updates; }
|
||||
bool isUsbLoggingEnabled() { return usb_logging; }
|
||||
void resumeUsbPacketLogging() {}
|
||||
bool isUsbDebugEnabled() { return usb_debug; }
|
||||
bool isUsbDebugLoggingEnabled() { return usb_logging && usb_debug; }
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ void setPlatformDebugOutputEnabled(bool){}
|
||||
void setUsbDebugEnabled(bool){}
|
||||
void setUsbLoggingEnabled(bool enabled){logging=enabled;usb_logging_preference_known=true;}
|
||||
bool saveUsbLoggingBootPreference(bool){return true;}
|
||||
unsigned packet_logging_resumes=0;
|
||||
void resumeUsbPacketLogging(){++packet_logging_resumes;}
|
||||
@USB_FUNCTIONS@
|
||||
void freshBoot(){
|
||||
dedicated_usb_logging_port_started=false;dedicated_usb_logging_port_configured=false;
|
||||
@@ -121,11 +123,15 @@ int main(){
|
||||
for(const char* invalid : {"set usb.logging stream extra","set usb.logging STREAM",
|
||||
"set usb.logging stream ","set usb.logging stream reboot ","set usb.loggingstream"}){
|
||||
const auto before=node.store.fs.files["/new_prefs"];
|
||||
const unsigned resumes=mesh::packet_logging_resumes;
|
||||
const bool handled=node.command(invalid,reply,sizeof(reply));
|
||||
assert(mesh::packet_logging_resumes==resumes);
|
||||
if(handled)assert(strstr(reply,"Error:"));
|
||||
assert(node.store.fs.files["/new_prefs"]==before&&node._prefs.usb_logging_enabled==1);
|
||||
}
|
||||
const unsigned resumes=mesh::packet_logging_resumes;
|
||||
assert(node.command("set usb.logging stream",reply,sizeof(reply)));
|
||||
assert(mesh::packet_logging_resumes==resumes+1);
|
||||
assert(node._prefs.usb_logging_enabled==2&&node.store.fs.files["/new_prefs"][158]==2);
|
||||
assert(strstr(reply,"reboot required"));assert(node._scheduled_reboot_at==0);
|
||||
assert(!mesh::isUsbLoggingPacketStream()); // Saved choice cannot flip a live descriptor/route.
|
||||
@@ -149,7 +155,12 @@ int main(){
|
||||
bootMode(node,1);node._scheduled_reboot_at=0;
|
||||
assert(node.command("set usb.logging stream",reply,sizeof(reply)));
|
||||
node.store.fs.fail_write=true;
|
||||
const unsigned before_failed_save=mesh::packet_logging_resumes;
|
||||
assert(node.command("set usb.logging off",reply,sizeof(reply)));
|
||||
for(const char* failed : {"set usb.logging on","set usb.logging stream"}) {
|
||||
assert(node.command(failed,reply,sizeof(reply)) && strstr(reply,"save failed"));
|
||||
assert(mesh::packet_logging_resumes==before_failed_save);
|
||||
}
|
||||
assert(strstr(reply,"save failed")&&node.store.fs.files["/new_prefs"][158]==2);
|
||||
assert(!mesh::isUsbLoggingPacketStream());
|
||||
char guarded[17];memset(guarded,'X',sizeof(guarded));
|
||||
|
||||
@@ -90,8 +90,9 @@ struct OtaContext {
|
||||
ArduinoSerialInterface usb_serial_interface;
|
||||
static Stream& usbTerminalOutput() { return input; }
|
||||
static bool connected=true, logging=false, dedicated=false;
|
||||
static std::vector<std::string> stats_commands;
|
||||
static std::vector<std::string> stats_commands, console_commands;
|
||||
namespace mesh {
|
||||
void noteUsbConsoleCommand(const char* command) { console_commands.emplace_back(command); }
|
||||
bool isUsbLoggingEnabled() { return logging; }
|
||||
bool hasDedicatedUsbLoggingPort() { return dedicated; }
|
||||
void noteUsbLoggingStatsCommand(const char* command) {
|
||||
@@ -154,7 +155,7 @@ static void drainUsbTerminalOutputBeforeProtocolSwitch() {}
|
||||
@FUNCTIONS@
|
||||
static void reset() {
|
||||
input=Input(); the_mesh=Mesh(); connected=true; logging=dedicated=false;
|
||||
stats_commands.clear();
|
||||
stats_commands.clear(); console_commands.clear();
|
||||
mesh::ota::OtaContext::serialFolderSource().resetSessionState();
|
||||
usb_serial_interface=ArduinoSerialInterface();
|
||||
usb_serial_interface.begin(input,USB_TERMINAL_START_TOKEN,USB_MOTA_START_TOKEN);
|
||||
@@ -192,18 +193,20 @@ int main() {
|
||||
assert(input.available()==5 && input.peek()=='<');
|
||||
assert(input.output.empty() && the_mesh.banners==0);
|
||||
finishProbe();
|
||||
assert(console_commands.empty());
|
||||
}
|
||||
// Empty Enter reveals the silent terminal without fabricating a command.
|
||||
for(const char* leading : {"\r","\n","\r\n"}) {
|
||||
reset(); input.push(leading); serviceUsbTerminal();
|
||||
assert(the_mesh.terminal && !the_mesh.waiting && the_mesh.banners==1);
|
||||
assert(!usb_binary_startup_probe.isActive() && input.available()==0);
|
||||
assert(the_mesh.commands.empty());
|
||||
assert(the_mesh.commands.empty() && console_commands.empty());
|
||||
serviceUsbTerminal(); assert(the_mesh.banners==1);
|
||||
}
|
||||
reset(); input.push("\r\nget name\r\n"); serviceUsbTerminal();
|
||||
assert(the_mesh.banners==1 && the_mesh.commands.size()==1);
|
||||
assert(the_mesh.commands[0]=="get name" && the_mesh.terminal);
|
||||
assert(console_commands==the_mesh.commands);
|
||||
// Reaching an empty prompt through an edit also leaves the frame untouched.
|
||||
reset(); input.push("x\b"); input.frame(); serviceUsbTerminal();
|
||||
assert(usb_binary_startup_probe.isActive() && input.available()==5);
|
||||
@@ -311,7 +314,7 @@ int main() {
|
||||
serviceUsbTerminal();
|
||||
assert(!usb_mota_mode && the_mesh.attaches==0 && the_mesh.detaches==0);
|
||||
assert(the_mesh.commands.size()==1 && the_mesh.commands[0]=="get name");
|
||||
assert(stats_commands==the_mesh.commands);
|
||||
assert(stats_commands==the_mesh.commands && console_commands==the_mesh.commands);
|
||||
const auto error=input.output.find("ERROR: invalid command");
|
||||
assert(error!=std::string::npos);
|
||||
assert(input.output.find("ERROR: invalid command",error+1)==std::string::npos);
|
||||
@@ -330,7 +333,7 @@ int main() {
|
||||
assert(oversized_setter.size()==610);
|
||||
reset(); input.hwcdcBurst(oversized_setter,256); serviceUsbTerminal();
|
||||
assert(input.rx_dropped==354 && input.output.size()==256+6); // Silent banner is revealed.
|
||||
assert(the_mesh.commands.empty() && usb_terminal_line_len==256);
|
||||
assert(the_mesh.commands.empty() && usb_terminal_line_len==256 && console_commands.empty());
|
||||
assert(!usb_terminal_discard_line && input.output.find("ERROR")==std::string::npos);
|
||||
input.push("\r"); serviceUsbTerminal();
|
||||
assert(the_mesh.commands==std::vector<std::string>{oversized_setter.substr(0,256)});
|
||||
|
||||
@@ -199,7 +199,7 @@ struct Port : Stream {
|
||||
} log_port;
|
||||
namespace mesh {
|
||||
bool enabled=true,debug_enabled=false;
|
||||
bool isUsbLoggingEnabled(){return enabled;}
|
||||
bool isUsbPacketLoggingAllowed(){return enabled;}
|
||||
Port& usbLoggingPort(){return log_port;}
|
||||
}
|
||||
struct Transport {
|
||||
|
||||
@@ -84,6 +84,7 @@ void setUsbLoggingEnabled(bool value) { logging=value; }
|
||||
void discardUsbTerminalOutput() {}
|
||||
Stream& usbCompanionPort() { return stream; }
|
||||
bool saveUsbLoggingBootPreference(bool) { return true; }
|
||||
void resumeUsbPacketLogging() {}
|
||||
bool usbLoggingInterfaceRestartRequired() { return false; }
|
||||
bool isUsbLoggingPacketStream() { return false; }
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ struct Port : Stream {
|
||||
|
||||
namespace mesh {
|
||||
bool logging_enabled = true;
|
||||
bool isUsbLoggingEnabled() { return logging_enabled; }
|
||||
bool isUsbPacketLoggingAllowed() { return logging_enabled; }
|
||||
Stream& usbLoggingPort() { return log_port; }
|
||||
void Utils::toHex(char* destination, const uint8_t* source, size_t size) {
|
||||
const char* digits = "0123456789ABCDEF";
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
"""Execute packet-only quiet periods with the real USB facade and RAW emitter."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_radio_receive_contract import method
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FIXTURE = ROOT / "test/fixtures/nrf52_usb_console"
|
||||
SANITIZERS = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
|
||||
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
|
||||
|
||||
CASES = r'''
|
||||
#include <helpers/SerialPacketLog.h>
|
||||
#include <helpers/BaseSerialInterface.h>
|
||||
@PUSH_CODE@
|
||||
struct PacketTransport {
|
||||
unsigned frames=0;
|
||||
bool isConnected() const {return true;}
|
||||
size_t writeFrame(const uint8_t*,size_t size){++frames;return size;}
|
||||
};
|
||||
struct MyMesh {
|
||||
PacketTransport transport;
|
||||
PacketTransport* _serial=&transport;
|
||||
uint8_t out_frame[MAX_FRAME_SIZE]={};
|
||||
const char* getLogDateTime(){return "stamp";}
|
||||
void logRxRaw(float,float,const uint8_t*,int);
|
||||
};
|
||||
@RAW_EMITTER@
|
||||
static void timerCases() {
|
||||
mesh::setUsbLoggingEnabled(true);mesh::resumeUsbPacketLogging();
|
||||
mesh::configureUsbLoggingPacketStream(false);g_mock_millis=0;
|
||||
for(const char* ignored:std::initializer_list<const char*>{nullptr,""," \t\r\n","stats-core"," \tstats-radio\t ","stats-packets",
|
||||
"\x01" "get name","get\x7f","\xff" "get name","get name\n","stats-core\r","get\rname"}) {
|
||||
mesh::noteUsbConsoleCommand(ignored);assert(mesh::isUsbPacketLoggingAllowed());
|
||||
}
|
||||
for(const char* human:{"get name","stats-core extra","stats-radiox","stats-packets 1"}) {
|
||||
mesh::noteUsbConsoleCommand(human);assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
mesh::resumeUsbPacketLogging();
|
||||
}
|
||||
mesh::noteUsbConsoleCommand("get name");
|
||||
assert(mesh::isUsbLoggingEnabled()&&!mesh::isUsbPacketLoggingAllowed());
|
||||
g_mock_millis=299999;assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
++g_mock_millis;assert(mesh::isUsbPacketLoggingAllowed());
|
||||
g_mock_millis+=0x80000000U;assert(mesh::isUsbPacketLoggingAllowed()); // Expiry latches.
|
||||
g_mock_millis=100;mesh::noteUsbConsoleCommand("get radio");
|
||||
g_mock_millis+=299000;mesh::noteUsbConsoleCommand("get name");
|
||||
g_mock_millis+=1000;assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
g_mock_millis+=298999;assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
++g_mock_millis;assert(mesh::isUsbPacketLoggingAllowed());
|
||||
g_mock_millis=UINT32_MAX-100000;mesh::noteUsbConsoleCommand("get name");
|
||||
g_mock_millis+=299999;assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
++g_mock_millis;assert(mesh::isUsbPacketLoggingAllowed());
|
||||
// Zero is the inactive sentinel; its single-tick coercion must stay bounded.
|
||||
g_mock_millis=uint32_t(0U-300000U);mesh::noteUsbConsoleCommand("get name");
|
||||
g_mock_millis=0;assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
g_mock_millis=1;assert(mesh::isUsbPacketLoggingAllowed());
|
||||
g_mock_millis=500;mesh::noteUsbConsoleCommand("get name");
|
||||
mesh::setUsbLoggingEnabled(true);assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
mesh::setUsbLoggingEnabled(false);g_mock_millis+=300000;
|
||||
assert(!mesh::isUsbPacketLoggingAllowed()&&!mesh::isUsbLoggingEnabled());
|
||||
g_mock_millis+=0x80000000U;mesh::setUsbLoggingEnabled(true);
|
||||
assert(mesh::isUsbPacketLoggingAllowed()); // Reapply never resurrects an expired pause.
|
||||
mesh::noteUsbConsoleCommand("get name");mesh::resumeUsbPacketLogging();
|
||||
assert(mesh::isUsbPacketLoggingAllowed());
|
||||
mesh::setUsbLoggingEnabled(false);mesh::resumeUsbPacketLogging();
|
||||
assert(!mesh::isUsbPacketLoggingAllowed()&&!mesh::isUsbLoggingEnabled());
|
||||
mesh::setUsbLoggingEnabled(true);mesh::configureUsbLoggingPacketStream(true);
|
||||
mesh::noteUsbConsoleCommand("get name");assert(mesh::isUsbPacketLoggingAllowed());
|
||||
mesh::configureUsbLoggingPacketStream(false);assert(mesh::isUsbPacketLoggingAllowed());
|
||||
std::puts("PASS: packet quiet timers, reset, wrap, polls, stream and saved master");
|
||||
}
|
||||
static void outputCases() {
|
||||
mesh::setUsbLoggingEnabled(true);mesh::setUsbDebugEnabled(true);
|
||||
mesh::resumeUsbPacketLogging();tud_cdc_line_state_cb(0,true,false);service();
|
||||
MyMesh node;const uint8_t raw[]={0x00,0xab,0xff};
|
||||
auto& console=mesh::usbConsolePort();
|
||||
const std::string reply="-> name: Companion\r\n";
|
||||
assert(console.write(reinterpret_cast<const uint8_t*>(reply.data()),reply.size())==reply.size());
|
||||
mesh::noteUsbConsoleCommand("get name");
|
||||
node.logRxRaw(2.5f,-92,raw,sizeof(raw));drain();
|
||||
if(host!=reply||node.transport.frames!=1)
|
||||
std::fprintf(stderr,"quiet host=[%s] bytes=%zu frames=%u allowed=%d\n",host.c_str(),host.size(),node.transport.frames,mesh::isUsbPacketLoggingAllowed());
|
||||
assert(host==reply&&node.transport.frames==1);
|
||||
assert(mesh::isUsbLoggingEnabled()&&mesh::isUsbDebugLoggingEnabled());
|
||||
assert(mesh::serialLogDroppedCount()==0);host.clear();
|
||||
mesh::resumeUsbPacketLogging();
|
||||
uint8_t large[160];std::fill(large,large+sizeof(large),0xa5);
|
||||
during_write=[] {mesh::noteUsbConsoleCommand("get radio");};
|
||||
node.logRxRaw(2.5f,-92,large,sizeof(large));drain();
|
||||
std::string hex;for(unsigned i=0;i<sizeof(large);++i)hex+="A5";
|
||||
#if MESH_PACKET_LOGGING_COMPACT
|
||||
assert(host=="R"+hex+"\n");
|
||||
#else
|
||||
assert(host=="stamp RAW: "+hex+"\r\n");
|
||||
#endif
|
||||
assert(!mesh::isUsbPacketLoggingAllowed()&&mesh::serialLogDroppedCount()==0);
|
||||
host.clear();node.logRxRaw(2.5f,-92,raw,sizeof(raw));drain();
|
||||
assert(host.empty()&&node.transport.frames==3);
|
||||
assert(console.write(reinterpret_cast<const uint8_t*>(reply.data()),reply.size())==reply.size());
|
||||
drain();assert(host==reply&&host.find('\x1b')==std::string::npos);
|
||||
assert(mesh::serialLogDroppedCount()==0);host.clear();
|
||||
mesh::resumeUsbPacketLogging();node.logRxRaw(2.5f,-92,raw,sizeof(raw));drain();
|
||||
#if MESH_PACKET_LOGGING_COMPACT
|
||||
assert(host=="R00ABFF\n");
|
||||
#else
|
||||
assert(host=="stamp RAW: 00ABFF\r\n");
|
||||
#endif
|
||||
std::puts("PASS: quiet RAW records preserve replies and complete in-progress records");
|
||||
}
|
||||
int main(int argc,char**) {if(argc==1)timerCases();else outputCases();}
|
||||
'''
|
||||
|
||||
CORE_CASE = r'''
|
||||
#include <helpers/UsbLogging.h>
|
||||
#include <helpers/UsbLoggingClientActivity.h>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
namespace mesh {
|
||||
static std::atomic<bool> usb_logging_enabled{true};
|
||||
static std::atomic<uint32_t> usb_packet_logging_quiet_until{0};
|
||||
bool dedicated=false,stream=false;
|
||||
bool hasDedicatedUsbLoggingPort(){return dedicated;}
|
||||
bool isUsbLoggingPacketStream(){return stream;}
|
||||
@METHODS@
|
||||
}
|
||||
int main() {
|
||||
mesh::noteUsbConsoleCommand("get name");assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
mesh::dedicated=true;assert(mesh::isUsbPacketLoggingAllowed());
|
||||
mesh::resumeUsbPacketLogging();mesh::noteUsbConsoleCommand("get name");
|
||||
mesh::dedicated=false;assert(mesh::isUsbPacketLoggingAllowed());
|
||||
mesh::dedicated=true;mesh::usb_logging_enabled=false;
|
||||
assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
class UsbPacketLoggingQuietTests(unittest.TestCase):
|
||||
def compile(self, work, name, text, flags=(), sources=()):
|
||||
source = work / (name + ".cpp")
|
||||
source.write_text(text)
|
||||
binary = work / (name + ".exe")
|
||||
built = subprocess.run([os.environ.get("CXX", "g++"), "-std=c++17", "-Wall", "-Wextra",
|
||||
"-Werror", *SANITIZERS, "-DARDUINO", *flags,
|
||||
"-I" + str(FIXTURE / "mocks"), "-I" + str(ROOT / "src"),
|
||||
str(source), *[str(path) for path in sources], "-o", str(binary)],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
||||
return binary
|
||||
|
||||
def test_actual_usb_facade_timers_and_raw_emitter_output(self):
|
||||
prefix = (FIXTURE / "test_console.cpp").read_text().split("int main()")[0]
|
||||
source = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text()
|
||||
cases = CASES.replace("@PUSH_CODE@", next(line for line in source.splitlines()
|
||||
if line.startswith("#define PUSH_CODE_LOG_RX_DATA")))
|
||||
cases = cases.replace("@RAW_EMITTER@", method(source, "void MyMesh::logRxRaw("))
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-packet-quiet-") as directory:
|
||||
work = Path(directory)
|
||||
for compact in (0, 1):
|
||||
with self.subTest(compact=compact):
|
||||
binary = self.compile(work, "quiet" + str(compact), prefix + cases,
|
||||
["-DNRF52_PLATFORM", "-DUSE_TINYUSB", "-DENABLE_USB_INTERFACE",
|
||||
"-DMESH_DEBUG=1", "-DMESH_PACKET_LOGGING=1", "-DMESH_PACKET_LOGGING_COMPACT=" + str(compact)],
|
||||
[ROOT / "src/helpers" / name for name in (
|
||||
"UsbLogging.cpp", "UsbLoggingClientActivity.cpp", "UsbLoggingLineStateOverride.cpp")])
|
||||
for arguments in ([], ["output"]):
|
||||
ran = subprocess.run([str(binary), *arguments], capture_output=True, text=True, timeout=20)
|
||||
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
|
||||
self.assertIn("PASS:", ran.stdout)
|
||||
|
||||
def test_actual_core_dedicated_endpoint_bypasses_automatic_pause(self):
|
||||
source = (ROOT / "src/helpers/UsbLogging.cpp").read_text()
|
||||
methods = "\n".join(method(source, signature) for signature in (
|
||||
"bool isUsbLoggingEnabled(", "void noteUsbConsoleCommand(",
|
||||
"void resumeUsbPacketLogging(", "bool isUsbPacketLoggingAllowed("))
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-packet-dedicated-") as directory:
|
||||
binary = self.compile(Path(directory), "dedicated", CORE_CASE.replace("@METHODS@", methods), ["-DMESH_DEBUG=1"])
|
||||
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=20)
|
||||
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
|
||||
|
||||
def test_disabled_logging_build_has_noop_pause_apis(self):
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-packet-disabled-") as directory:
|
||||
for flags in ([], ["-DMESH_DEBUG=1", "-DMESH_USB_LOGGING_DISABLED=1"]):
|
||||
binary = self.compile(Path(directory), "disabled", '''
|
||||
#include <helpers/UsbLogging.h>
|
||||
#include <cassert>
|
||||
int main(){
|
||||
static_assert(!MESH_USB_LOGGING_AVAILABLE,"disabled test profile");
|
||||
mesh::noteUsbConsoleCommand("get name");mesh::resumeUsbPacketLogging();
|
||||
assert(!mesh::isUsbPacketLoggingAllowed());
|
||||
}
|
||||
''', flags)
|
||||
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=20)
|
||||
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user