Skip to main content

run-meshtastic-android

Run, launch, drive, and screenshot the Meshtastic app — the Compose Desktop app via hot reload (semantic clicks, live reload, window screenshots) or the Android app on an emulator (scripted deeplink bring-up, uiautomator taps, screencap). Use when asked to run the app, verify a UI change in the real app, take a screenshot, or exercise a flow end to end against a simulated radio.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
meshtastic/Meshtastic-Android
آخر نشاط في المصدر
٣٠ أغسطس ٢٠٢٦ في ٠١:٢١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٬٨٤٧
التفرعات
٥١٧

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
3 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
run-meshtastic-android
description
Run, launch, drive, and screenshot the Meshtastic app — the Compose Desktop app via hot reload (semantic clicks, live reload, window screenshots) or the Android app on an emulator (scripted deeplink bring-up, uiautomator taps, screencap). Use when asked to run the app, verify a UI change in the real app, take a screenshot, or exercise a flow end to end against a simulated radio.
# Run Meshtastic (Desktop & Emulator) Two binaries, two drivers, one simulated radio. All paths are relative to the repo root. Both drivers are Python 3, stdlib only, and print `--- <cmd> done ---` per step on stderr. - **Desktop** (`:desktopApp`, Compose/JVM, runs on this machine): launch with the hot-reload run task, drive through `.claude/skills/run-meshtastic-android/driver.py`, which speaks MCP JSON-RPC to `:desktopApp:hotMcpServer` — semantic tree, clicks by node id, `reload` (recompile + hot-swap), window screenshots. - **Emulator** (`:androidApp` fdroid debug): drive through `.claude/skills/run-meshtastic-android/driver_emulator.py` — scripted deeplink bring-up, uiautomator-based taps, screencap. - **Radio**: neither app does much without one. `mcp__meshtastic__replay_start` (meshtastic MCP) serves a simulated Meshtastic TCP radio; the desktop app reaches it at `127.0.0.1:<port>`, an AVD at `10.0.2.2:<port>`. One client per session — run the desktop and emulator against **different ports** (e.g. 4403 and 4404). ## Prerequisites - Gradle runs go through the machine-wide queue: `~/.claude/bin/gradle-queue`. Everything after its `--` is **Gradle arguments** — it runs `./gradlew` itself (`gradle-queue -- ./gradlew tasks` fails with `Task './gradlew' not found`). - The JetBrains 25 JDK Gradle provisioned at `~/.gradle/jdks/jetbrains_s_r_o_-25-*/…/Contents/Home` (the drivers find it themselves). - Emulator leg: a running AVD (`adb devices`) with the fdroid debug build installed (`./gradlew :androidApp:installFdroidDebug` via the queue if missing). - A simulated radio, e.g. `replay_start(source="meshcon", sim_nodes=30, port=4403, rate=2, loop=true, sim_profile={"traceroute_pairs_per_hour": 0})` — mute the traceroutes or their modals bury whatever you are testing. ## Run: Desktop (agent path) Kill stray instances first — two apps fight over the pid file and the MCP server reports `connected:false` forever: ```bash pgrep -fl "MainKt|devtools.Main" # kill any hits before launching ``` Launch (the Nix dev shell's Darwin stdenv breaks the MapLibre FFI — strip it): ```bash env -u DEVELOPER_DIR -u SDKROOT -u CC -u CXX -u LD -u AR -u NM -u RANLIB -u STRIP -u NIX_CC \ JAVA_HOME=$(ls -d ~/.gradle/jdks/jetbrains_s_r_o_-25-*/*/Contents/Home | tail -1) \ PATH=/usr/bin:/bin:/usr/sbin:/sbin \ ~/.claude/bin/gradle-queue -- :desktopApp:hotRunAsync ``` `BUILD SUCCESSFUL` + `desktopApp/build/run/main/main.pid` on disk means the app is up. Drive it. Each driver invocation spawns a fresh `hotMcpServer`, auto-waits for it to attach (asynchronous — the driver polls `status` for you), runs the commands in order, and exits: ```bash python3 .claude/skills/run-meshtastic-android/driver.py tree # semantic tree (JSON, node ids) python3 .claude/skills/run-meshtastic-android/driver.py click=170 sleep=1.5 tree python3 .claude/skills/run-meshtastic-android/driver.py raise ss=/tmp/app.png python3 .claude/skills/run-meshtastic-android/driver.py reload # recompile + hot-swap edits ``` Run `driver.py` with no arguments for the full command list (`type=NODEID:TEXT`, `scroll_to=NODEID:IDX`, `restart`, `err`, `logs`, …). `tools` prints the server's live tool schemas if they've drifted. **Verified flow** (connect to a sim and see its mesh): nav-rail tabs are semantic `Tab` nodes — `Connect` opened via `click=<its id from tree>`, then the `Network` radio button, then the device row for `127.0.0.1` under Recent Network Devices. The sim's `replay_status` flips to `connected:true` within seconds and the Nodes tab fills with the sim's mesh (`RPLY Replay Observer`, …). The connection card can sit on "Reconnecting…" while packets already flow — the label lags the config download. Trust `replay_status` and the Nodes list, not the card text. **Screenshots capture the window's on-screen region**, so the window must be frontmost: always `raise` before `ss`. If `ss` shows your terminal, that's why. `hotMcpServer` and `reload` compile **outside** gradle-queue (a long-lived stdio server can't hold a slot) — check `~/.claude/bin/gradle-queue --status` before a `reload` if other sessions may be building, and keep those runs short. ### Desktop deeplink launch (no clicking — but no hot reload) The desktop app parses the same Meshtastic deeplink URIs from its **program args** (`Main.kt` accepts `meshtastic://` and `https://meshtastic.org/...`), so a connected app is one command: ```bash env -u DEVELOPER_DIR -u SDKROOT -u CC -u CXX -u LD -u AR -u NM -u RANLIB -u STRIP -u NIX_CC \ JAVA_HOME=$(ls -d ~/.gradle/jdks/jetbrains_s_r_o_-25-*/*/Contents/Home | tail -1) \ PATH=/usr/bin:/bin:/usr/sbin:/sbin \ ~/.claude/bin/gradle-queue -- :desktopApp:run --args="https://meshtastic.org/connections?address=t127.0.0.1:4403" ``` Verified against a sim the app had never connected to before, so it is the deeplink acting, not last-device auto-reconnect. Caveats, all observed: - `hotRunAsync` does **not** accept `--args` (its option list: --auto, --className, --funName, --mainClass, --stdout/--stderr only) — deeplink launch means the plain `run` task, which trades away hot reload. Long driving session → `hotRunAsync` + the driver's click path; quick "get me a connected app" → `run --args=…`. - `run` blocks, so it **holds a gradle-queue slot for the app's whole lifetime**. Keep such runs short, or other sessions' builds will queue behind your app. - The deeplink races last-device auto-reconnect: the app can connect to its remembered device first, then switch to the deeplink's target a moment later — if the remembered device is another sim, that sim briefly shows a client too. - No trust dialog blocked the localhost connect in testing (unlike the Android build, which pops one for a never-seen device). ## Run: Emulator (agent path) Scripted bring-up only — never hand-walk onboarding or the manual-IP dialog: ```bash python3 .claude/skills/run-meshtastic-android/driver_emulator.py -s emulator-5554 \ connect=t10.0.2.2:4404 wait_text=RPLY ss=/tmp/emu.png ``` `connect` force-stops the app, relaunches `org.meshtastic.app.MainActivity` with the debug-only `skip_onboarding` extra and the `/connections?address=` deeplink (`t` = TCP, `x` = BLE, `s` = serial, `n` = disconnect — full path list in `docs/en/developer/navigation-and-deep-links.md`), then waits for the trust dialog newer builds pop and taps its **Connect** button. Success looks like the Connection screen showing `RPLY Replay Observer` with a **Disconnect** button, and `replay_status` reporting `connected:true`. Other commands: `dump`, `find=TEXT`, `tap_text=TEXT`, `tap=X,Y`, `text=`, `key=`, `swipe=`, `launch`, `stop` — run with no arguments for the list. Default package is `com.geeksville.mesh.fdroid.debug` (`-p` to override). ## Run (human path) `./gradlew :desktopApp:run` (via the queue, same env hygiene) opens the window without hot reload; Ctrl-C to stop. The emulator app is just the launcher icon — but a debug build launched by icon lands on onboarding; the deeplink path above is faster even for humans. ## Stopping - Desktop: take the pid from the app's own pid file — it is a Java properties file (not a bare pid) and self-deletes on clean exit: ```bash kill $(sed -n 's/^pid=//p' desktopApp/build/run/main/main.pid) ``` If the pid file is gone but a process lingers, `pgrep -af "MainKt|devtools.Main"`, check each match's path for **this** checkout, and kill that specific PID — a bare `pkill` on the pattern can take down another checkout's or session's app. - Emulator: `driver_emulator.py -s <serial> stop`. - Sim: `replay_stop`. Sessions the sim created are real user data in the app's DB; the app's last-selected device is now the sim — switch back on the Connect screen if a real radio should reconnect. ## Gotchas - **`gradle-queue -- ./gradlew …` fails**: args after `--` go to `./gradlew`, which the wrapper runs itself. And piping its output (`| tail`) eats the exit code — check for `BUILD SUCCESSFUL` in the text, not `$?`. - **`tap_text` matches substrings**: bare `Connect` also matches "Stop **Connect**ing" and "Re**connect**ing…". The driver tries exact text first; wait on the trust dialog's title ("Connect to this device"), not its button. - **The MCP server attaches asynchronously** — a `tree` fired immediately after spawn returns "No application is currently connected". The driver auto-waits; if it times out, the app isn't running (or a stray instance holds the pid file). - **`take_screenshot` needs the window visible** — `raise` first (System Events `AXRaise` targeting the window literally named "Meshtastic Desktop"; with two java processes, pid-based frontmosting picks the wrong one). - **One client per simulated node.** Two apps pointed at the same sim don't queue — they fight, stealing the connection back and forth so both flap between Connected and Reconnecting. The desktop app holding port 4403 means the emulator needs its own `replay_start` on 4404. - **`adb shell input text` can leave a trailing space**; dialogs' Add buttons silently no-op on it. And don't press BACK to dismiss the keyboard — it closes the dialog. - **Swipe near x≈30** in lists; mid-screen swipes get eaten by embedded maps. Never busy-loop adb — pace with `adb shell sleep 2` or the emulator drops offline. - The desktop app auto-reconnects to its last device on launch — it may already be connected to a real radio when you attach; check the Connect screen before assuming the sim. ## Troubleshooting - `Task './gradlew' not found in root project` → you passed `./gradlew` after `gradle-queue --`; drop it. - `BUILD FAILED in 1s` from `hotRunAsync` with slots free → read the full output; the queue wrapper's exit code vanishes behind pipes. - Screenshot is your terminal → `raise` before `ss` (window wasn't frontmost). - `connected:false` forever from `status` → stray `MainKt` from another checkout or worktree; `pgrep -fl MainKt`, kill, relaunch. - Trust dialog never tapped, app stuck on dialog → older driver matched "Reconnecting…"; re-run `tap_text=Connect` (exact match wins now). - UI card stuck "Reconnecting…" but sim says `connected:true` → not stuck; config download in progress. Check the Nodes tab for the sim's nodes. - Connection flapping → `desktopApp/build/run/main/hotRun.stderr.txt` carries the transport-level story ("Handshake stall detected at Stage 1 … requesting forced transport restart" is the app self-recovering, not a crash). Also check that a second app isn't fighting for the same sim (one client per simulated node). - A bare `status` right after spawn can report `connected:false` while the app is fine — the server attach is asynchronous; `wait` (or any UI command, which auto-waits) is the truth.
عرض على GitHub