310 lines
10 KiB
Bash
Executable File
310 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
FIRMWARE_DIR="$SCRIPT_DIR/velxio-deployer-firmware"
|
|
IDF_PATH="$PARENT_DIR/esp-idf-v6"
|
|
WS_URL_FILE="/tmp/opencode/cdp-ws-url"
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
|
|
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
err() { echo -e "${RED}[ERR]${NC} $1" >&2; }
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: debug-capture.sh <command> [options]
|
|
|
|
Commands:
|
|
setup Detect ADB device, setup forward, find Chrome tab
|
|
cdp [ws_url] Stream Chrome console.log via CDP (inline Node.js)
|
|
firmware Source IDF + run idf.py monitor on /dev/ttyACM0
|
|
all [ws_url] Run CDP + firmware simultaneously
|
|
sw-clear [ws_url] Unregister SW + clear caches + reload page on device
|
|
eval '<js>' Evaluate JS expression on device, print result
|
|
status Check connection status (ADB, CDP, Chrome)
|
|
help Show this help
|
|
EOF
|
|
}
|
|
|
|
detect_adb() {
|
|
local devices
|
|
devices=$(adb devices 2>/dev/null | grep -v "List of devices attached" | grep -v "^$" | awk '{print $1}')
|
|
if [ -z "$devices" ]; then
|
|
err "No ADB device detected. Check USB/WiFi connection."
|
|
return 1
|
|
fi
|
|
local count; count=$(echo "$devices" | wc -l)
|
|
[ "$count" -gt 1 ] && warn "Multiple ADB devices. Using first."
|
|
echo "$devices" | head -1
|
|
}
|
|
|
|
setup_forward() {
|
|
adb forward tcp:9222 localabstract:chrome_devtools_remote >/dev/null 2>&1
|
|
local fwd; fwd=$(adb forward --list | grep tcp:9222)
|
|
[ -z "$fwd" ] && { err "ADB forward tcp:9222 failed"; return 1; }
|
|
ok "ADB forward: $fwd"
|
|
}
|
|
|
|
detect_tab() {
|
|
local filter="${1:-sinau-c-dev}"
|
|
local resp; resp=$(curl -s http://localhost:9222/json/list 2>/dev/null)
|
|
[ -z "$resp" ] && { err "CDP not responding at http://localhost:9222"; return 1; }
|
|
local ws_url
|
|
ws_url=$(echo "$resp" | python3 -c "
|
|
import sys, json
|
|
tabs = json.load(sys.stdin)
|
|
term = '$filter'
|
|
for t in tabs:
|
|
if t.get('type') == 'page' and term in t.get('url', ''):
|
|
print(t.get('webSocketDebuggerUrl', ''))
|
|
sys.exit(0)
|
|
# fallback: first page tab
|
|
for t in tabs:
|
|
if t.get('type') == 'page':
|
|
print(t.get('webSocketDebuggerUrl', ''))
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
" 2>/dev/null)
|
|
if [ -z "$ws_url" ]; then
|
|
err "No page tab found. Listing available tabs:"
|
|
echo "$resp" | python3 -c "
|
|
import sys, json
|
|
for t in json.load(sys.stdin):
|
|
if t.get('type') == 'page':
|
|
print(f\" {t.get('title','?')[:50]:50s} {t.get('url','?')[:80]}\")
|
|
" 2>/dev/null
|
|
return 1
|
|
fi
|
|
echo "$ws_url"
|
|
}
|
|
|
|
source_idf() {
|
|
[ ! -f "$IDF_PATH/export.sh" ] && { err "IDF not found at $IDF_PATH"; return 1; }
|
|
. "$IDF_PATH/export.sh" >/dev/null 2>&1
|
|
}
|
|
|
|
get_ws_url() {
|
|
local url="$1"
|
|
[ -n "$url" ] && { echo "$url"; return 0; }
|
|
[ -f "$WS_URL_FILE" ] && { cat "$WS_URL_FILE"; return 0; }
|
|
setup_forward 2>/dev/null
|
|
detect_tab
|
|
}
|
|
|
|
CDP_NODE_SCRIPT='
|
|
const wsUrl = process.env.WS_URL;
|
|
const ws = new WebSocket(wsUrl);
|
|
let msgId = 0;
|
|
function cmd(m, p = {}) { return JSON.stringify({ id: ++msgId, method: m, params: p }); }
|
|
ws.addEventListener("open", () => {
|
|
ws.send(cmd("Runtime.enable"));
|
|
ws.send(cmd("Console.enable"));
|
|
ws.send(cmd("Runtime.runIfWaitingForDebugger"));
|
|
});
|
|
ws.addEventListener("message", (ev) => {
|
|
let msg;
|
|
try { msg = JSON.parse(ev.data); } catch { return; }
|
|
if (msg.method === "Runtime.consoleAPICalled") {
|
|
const text = msg.params.args.map(a => a.type === "string" ? a.value : JSON.stringify(a.value ?? a)).join(" ");
|
|
console.log(text);
|
|
} else if (msg.method === "Console.messageAdded") {
|
|
console.log("[CONSOLE]", msg.params.message.text);
|
|
} else if (msg.method === "Runtime.exceptionThrown") {
|
|
const d = msg.params.exceptionDetails;
|
|
console.error("[EXCEPTION]", d.text, "at", d.url + ":" + d.lineNumber);
|
|
}
|
|
});
|
|
ws.addEventListener("error", () => process.exit(1));
|
|
ws.addEventListener("close", () => process.exit(0));
|
|
'
|
|
|
|
case "$1" in
|
|
setup)
|
|
echo "=== Setup CDP Connection ==="
|
|
detect_adb || exit 1
|
|
setup_forward || exit 1
|
|
info "Waking up device..."
|
|
adb shell input keyevent 82 2>/dev/null; sleep 0.5
|
|
adb shell input keyevent 82 2>/dev/null
|
|
info "Waiting for Chrome devtools..."
|
|
for i in $(seq 1 10); do
|
|
curl -s -o /dev/null -w "%{http_code}" http://localhost:9222/json/list 2>/dev/null | grep -q 200 && break
|
|
[ "$i" -eq 10 ] && { err "Chrome devtools not responding."; exit 1; }
|
|
sleep 1
|
|
done
|
|
ok "Chrome devtools ready"
|
|
ws_url=$(detect_tab "${2:-sinau-c-dev}") || exit 1
|
|
ok "Tab found"
|
|
echo "$ws_url" > "$WS_URL_FILE"
|
|
ok "WS URL saved to $WS_URL_FILE"
|
|
echo ""
|
|
echo "Next: ./debug-capture.sh cdp # Stream console.log"
|
|
echo " ./debug-capture.sh firmware # Firmware log"
|
|
echo " ./debug-capture.sh all # Both at once"
|
|
;;
|
|
|
|
cdp)
|
|
ws_url=$(get_ws_url "$2") || { err "No CDP WS URL. Run 'setup' first."; exit 1; }
|
|
ok "Connecting to CDP"
|
|
info "Streaming console.log... Ctrl+C to stop."
|
|
echo "---"
|
|
WS_URL="$ws_url" node -e "$CDP_NODE_SCRIPT"
|
|
;;
|
|
|
|
firmware)
|
|
source_idf || exit 1
|
|
info "Starting firmware monitor on /dev/ttyACM0... Ctrl+C to stop."
|
|
echo "---"
|
|
cd "$FIRMWARE_DIR" || exit 1
|
|
idf.py -p /dev/ttyACM0 monitor
|
|
;;
|
|
|
|
all)
|
|
ws_url=$(get_ws_url "$2") || { err "No CDP WS URL. Run 'setup' first."; exit 1; }
|
|
source_idf || exit 1
|
|
info "Starting CDP monitor (background) + firmware monitor (foreground)..."
|
|
echo "---"
|
|
WS_URL="$ws_url" node -e "$CDP_NODE_SCRIPT" &
|
|
CDP_PID=$!
|
|
info "CDP PID: $CDP_PID"
|
|
cd "$FIRMWARE_DIR" || exit 1
|
|
idf.py -p /dev/ttyACM0 monitor
|
|
kill "$CDP_PID" 2>/dev/null
|
|
wait "$CDP_PID" 2>/dev/null
|
|
;;
|
|
|
|
sw-clear)
|
|
ws_url=$(get_ws_url "$2") || { err "No CDP WS URL. Run 'setup' first."; exit 1; }
|
|
info "Clearing Service Worker cache on device..."
|
|
WS_URL="$ws_url" node -e '
|
|
const wsUrl = process.env.WS_URL;
|
|
const ws = new WebSocket(wsUrl);
|
|
let msgId = 0;
|
|
function cmd(m, p = {}) { return JSON.stringify({ id: ++msgId, method: m, params: p }); }
|
|
ws.addEventListener("open", () => {
|
|
ws.send(cmd("Runtime.enable"));
|
|
ws.send(cmd("Runtime.runIfWaitingForDebugger"));
|
|
setTimeout(() => {
|
|
ws.send(cmd("Runtime.evaluate", {
|
|
expression: `(async () => {
|
|
const regs = await navigator.serviceWorker.getRegistrations();
|
|
for (const r of regs) {
|
|
await r.unregister();
|
|
console.log("SW unregistered:", r.active?.scriptURL || "unknown");
|
|
}
|
|
const keys = await caches.keys();
|
|
for (const k of keys) {
|
|
await caches.delete(k);
|
|
console.log("Cache deleted:", k);
|
|
}
|
|
return { unregistered: regs.length, cachesCleared: keys.length };
|
|
})()`,
|
|
awaitPromise: true
|
|
}));
|
|
}, 1000);
|
|
});
|
|
ws.addEventListener("message", (ev) => {
|
|
let msg;
|
|
try { msg = JSON.parse(ev.data); } catch { return; }
|
|
if (msg.method === "Runtime.consoleAPICalled") {
|
|
const text = msg.params.args.map(a => a.value || JSON.stringify(a)).join(" ");
|
|
console.log(text);
|
|
}
|
|
if (msg.id && msg.result) {
|
|
if (msg.result.result?.value) {
|
|
console.log("Result:", JSON.stringify(msg.result.result.value));
|
|
}
|
|
setTimeout(() => {
|
|
ws.send(cmd("Page.enable"));
|
|
setTimeout(() => {
|
|
ws.send(cmd("Page.reload", { ignoreCache: true }));
|
|
console.error("[CDP] Page reloaded");
|
|
setTimeout(() => { ws.close(); process.exit(0); }, 2000);
|
|
}, 300);
|
|
}, 500);
|
|
}
|
|
});
|
|
ws.addEventListener("error", () => process.exit(1));
|
|
setTimeout(() => { console.error("Timeout"); process.exit(1); }, 20000);
|
|
'
|
|
;;
|
|
|
|
eval)
|
|
[ -z "$2" ] && { err "Usage: $(basename "$0") eval '\''<js_expression>'\''"; exit 1; }
|
|
ws_url=$(get_ws_url "$3") || { err "No CDP WS URL. Run 'setup' first."; exit 1; }
|
|
info "Evaluating: $2"
|
|
WS_URL="$ws_url" JS_EXPR="$2" node -e '
|
|
const wsUrl = process.env.WS_URL;
|
|
const expr = process.env.JS_EXPR;
|
|
const ws = new WebSocket(wsUrl);
|
|
let msgId = 0;
|
|
function cmd(m, p = {}) { return JSON.stringify({ id: ++msgId, method: m, params: p }); }
|
|
ws.addEventListener("open", () => {
|
|
ws.send(cmd("Runtime.enable"));
|
|
ws.send(cmd("Runtime.runIfWaitingForDebugger"));
|
|
setTimeout(() => {
|
|
ws.send(cmd("Runtime.evaluate", {
|
|
expression: "(async () => { try { return await (" + expr + "); } catch(e) { return {error: e?.message || String(e)}; } })()",
|
|
awaitPromise: true
|
|
}));
|
|
}, 500);
|
|
});
|
|
ws.addEventListener("message", (ev) => {
|
|
let msg;
|
|
try { msg = JSON.parse(ev.data); } catch { return; }
|
|
if (msg.id && msg.result) {
|
|
const r = msg.result.result;
|
|
if (r) {
|
|
if (r.type === "string") console.log(r.value);
|
|
else console.log(JSON.stringify(r.value ?? r.description ?? r, null, 2));
|
|
}
|
|
ws.close();
|
|
process.exit(0);
|
|
}
|
|
});
|
|
ws.addEventListener("error", () => process.exit(1));
|
|
setTimeout(() => { console.error("Timeout"); process.exit(1); }, 15000);
|
|
'
|
|
;;
|
|
|
|
status)
|
|
echo "=== Debug Connection Status ==="
|
|
if adb_devices=$(adb devices 2>/dev/null | grep -v "List of devices attached" | grep -v "^$") && [ -n "$adb_devices" ]; then
|
|
echo "$adb_devices" | while IFS= read -r line; do ok "ADB: $line"; done
|
|
else
|
|
err "ADB: No device"
|
|
fi
|
|
if fwd=$(adb forward --list 2>/dev/null | grep tcp:9222) && [ -n "$fwd" ]; then
|
|
ok "Forward: $fwd"
|
|
else
|
|
err "Forward: No tcp:9222"
|
|
fi
|
|
cdp_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:9222/json/version 2>/dev/null)
|
|
if [ "$cdp_code" = "200" ]; then
|
|
browser=$(curl -s http://localhost:9222/json/version 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('Browser','?'))" 2>/dev/null)
|
|
ok "CDP: OK ($browser)"
|
|
tab=$(detect_tab 2>/dev/null)
|
|
[ -n "$tab" ] && ok "Tab: Found" || warn "Tab: No lesson tab"
|
|
else
|
|
err "CDP: Not responding"
|
|
fi
|
|
[ -c /dev/ttyACM0 ] && ok "ACM0: Present" || err "ACM0: Missing"
|
|
[ -f "$IDF_PATH/export.sh" ] && ok "IDF: $IDF_PATH" || err "IDF: Not found"
|
|
[ -f "$WS_URL_FILE" ] && ok "WS URL file: Exists" || warn "WS URL file: Missing"
|
|
nv=$(node --version 2>/dev/null)
|
|
[ -n "$nv" ] && ok "Node: $nv" || err "Node: Not found"
|
|
;;
|
|
|
|
--help|-h|help|"")
|
|
usage
|
|
;;
|
|
|
|
*)
|
|
err "Unknown command: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|