Restore Companion text terminal after USB mOTA sender exits
This commit is contained in:
1 parent
ce46e60a86
commit
9ba79848ca
2 files changed
+120
-1
No files matched your search
@@ -620,6 +620,8 @@ static mesh::UsbBinaryStartupProbe usb_binary_startup_probe;
|
||||
static mesh::UsbAsciiSessionDefault usb_ascii_session_default;
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
static bool usb_mota_mode = false;
|
||||
static mesh::UsbMotaEntryOrigin usb_mota_entry_origin =
|
||||
mesh::UsbMotaEntryOrigin::BINARY;
|
||||
static char usb_mota_line[32];
|
||||
static size_t usb_mota_line_len = 0;
|
||||
static bool usb_mota_disconnect_armed = false;
|
||||
@@ -779,16 +781,23 @@ static void resetUsbMotaMode() {
|
||||
}
|
||||
|
||||
static void leaveUsbMotaMode(bool acknowledge) {
|
||||
const bool restore_ascii = acknowledge
|
||||
&& usb_mota_entry_origin == mesh::UsbMotaEntryOrigin::ASCII;
|
||||
char reply[160] = {0};
|
||||
the_mesh.handleLocalControlCommand("ota folder off", reply, sizeof(reply));
|
||||
if (acknowledge) {
|
||||
char transition_reply[224];
|
||||
snprintf(transition_reply, sizeof(transition_reply),
|
||||
"\r\n%s\r\nOK - Binary mode\r\n", reply);
|
||||
"\r\n%s\r\nOK - %s mode\r\n", reply,
|
||||
restore_ascii ? "Terminal" : "Binary");
|
||||
queueUsbTerminalControlReply(transition_reply);
|
||||
drainUsbTerminalOutputBeforeProtocolSwitch();
|
||||
}
|
||||
resetUsbMotaMode();
|
||||
// A text-first motatool session must hand CDC0 back to the text terminal.
|
||||
// Otherwise closing the sender leaves this still-open USB host in Binary
|
||||
// mode, and its next `ver` or `ota status` appears to get no reply.
|
||||
if (restore_ascii) enterUsbTerminalMode(false);
|
||||
}
|
||||
|
||||
static bool enterUsbMotaMode(mesh::UsbMotaEntryOrigin origin) {
|
||||
@@ -796,6 +805,7 @@ static bool enterUsbMotaMode(mesh::UsbMotaEntryOrigin origin) {
|
||||
mesh::discardUsbTerminalOutput();
|
||||
usb_serial_interface.setPassthroughMode(true);
|
||||
usb_mota_mode = true;
|
||||
usb_mota_entry_origin = origin;
|
||||
usb_mota_line_len = 0;
|
||||
usb_mota_line[0] = 0;
|
||||
usb_mota_disconnect_armed = isUsbTerminalDataConnected();
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Execute the Companion's real USB mOTA exit functions with both entry modes."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
MAIN = (Path(__file__).resolve().parents[1] / "examples/companion_radio/main.cpp").read_text()
|
||||
|
||||
|
||||
def function(signature):
|
||||
start = MAIN.index(signature)
|
||||
end = MAIN.index("{", start) + 1
|
||||
depth = 1
|
||||
while depth:
|
||||
depth += (MAIN[end] == "{") - (MAIN[end] == "}")
|
||||
end += 1
|
||||
return MAIN[start:end]
|
||||
|
||||
|
||||
HARNESS = r'''
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
namespace mesh {
|
||||
enum class UsbMotaEntryOrigin { BINARY, ASCII };
|
||||
static void discardUsbTerminalOutput() {}
|
||||
}
|
||||
static bool usb_mota_mode = true, usb_mota_disconnect_armed = true;
|
||||
static mesh::UsbMotaEntryOrigin usb_mota_entry_origin = mesh::UsbMotaEntryOrigin::BINARY;
|
||||
static char usb_mota_line[32] = "old";
|
||||
static size_t usb_mota_line_len = 3;
|
||||
static bool ascii = false;
|
||||
static int drained = 0, folder_off = 0;
|
||||
static char control_reply[224];
|
||||
struct SerialInterface {
|
||||
bool passthrough = true;
|
||||
void setPassthroughMode(bool enabled) { passthrough = enabled; }
|
||||
} usb_serial_interface;
|
||||
struct Mesh {
|
||||
bool handleLocalControlCommand(const char* command, char* reply, size_t size) {
|
||||
assert(strcmp(command, "ota folder off") == 0);
|
||||
++folder_off;
|
||||
snprintf(reply, size, "OK folder off");
|
||||
return true;
|
||||
}
|
||||
} the_mesh;
|
||||
static void queueUsbTerminalControlReply(const char* reply) {
|
||||
snprintf(control_reply, sizeof(control_reply), "%s", reply);
|
||||
}
|
||||
static void drainUsbTerminalOutputBeforeProtocolSwitch() { ++drained; }
|
||||
static void enterUsbTerminalMode(bool show_banner) {
|
||||
assert(!show_banner);
|
||||
ascii = true;
|
||||
usb_serial_interface.setPassthroughMode(true);
|
||||
}
|
||||
@FUNCTIONS@
|
||||
static void prepare(mesh::UsbMotaEntryOrigin origin) {
|
||||
usb_mota_mode = usb_mota_disconnect_armed = true;
|
||||
usb_mota_entry_origin = origin;
|
||||
usb_mota_line_len = 3;
|
||||
strcpy(usb_mota_line, "old");
|
||||
usb_serial_interface.passthrough = true;
|
||||
ascii = false;
|
||||
drained = folder_off = 0;
|
||||
control_reply[0] = 0;
|
||||
}
|
||||
int main() {
|
||||
prepare(mesh::UsbMotaEntryOrigin::ASCII);
|
||||
leaveUsbMotaMode(true);
|
||||
assert(ascii && usb_serial_interface.passthrough);
|
||||
assert(!usb_mota_mode && !usb_mota_disconnect_armed && usb_mota_line_len == 0);
|
||||
assert(folder_off == 1 && drained == 1);
|
||||
assert(strstr(control_reply, "OK - Terminal mode") != nullptr);
|
||||
|
||||
prepare(mesh::UsbMotaEntryOrigin::BINARY);
|
||||
leaveUsbMotaMode(true);
|
||||
assert(!ascii && !usb_serial_interface.passthrough);
|
||||
assert(folder_off == 1 && drained == 1);
|
||||
assert(strstr(control_reply, "OK - Binary mode") != nullptr);
|
||||
|
||||
prepare(mesh::UsbMotaEntryOrigin::ASCII);
|
||||
leaveUsbMotaMode(false); // Host reset owns the next protocol decision.
|
||||
assert(!ascii && !usb_serial_interface.passthrough);
|
||||
assert(folder_off == 1 && drained == 0 && control_reply[0] == 0);
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
class UsbMotaExitTests(unittest.TestCase):
|
||||
def test_clean_text_sender_restores_text_terminal(self):
|
||||
source = HARNESS.replace(
|
||||
"@FUNCTIONS@",
|
||||
function("static void resetUsbMotaMode()") + "\n"
|
||||
+ function("static void leaveUsbMotaMode(bool acknowledge)"),
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
cpp = Path(directory) / "test.cpp"
|
||||
binary = Path(directory) / "test"
|
||||
cpp.write_text(source)
|
||||
subprocess.run(["c++", "-std=c++11", str(cpp), "-o", str(binary)],
|
||||
check=True, capture_output=True)
|
||||
subprocess.run([str(binary)], check=True, capture_output=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user