Android foreground-service app that acts as a remote peer for meshbot. The phone dials meshbot over TCP (typically across WireGuard), then exposes three capabilities over that one link: remote control of meshbot's hooks, a remote view of meshbot's console, and optionally bridging a locally-attached Meshtastic radio (USB OTG or BLE) into meshbot as an "attached device" interface. On the wire the app identifies itself as meshbot-remote.
Project home: officialmesh.org · github.com/official-mesh
Maintainer: The Official Mesh Admin
License: GNU Affero General Public License v3.0-only — see LICENSE
Companion to meshbot, which targets Meshtastic mesh radios. "Meshtastic" is referenced nominatively. This project is not affiliated with, endorsed by, licensed from, or authorized by Meshtastic LLC or its maintainers.
This app ships alongside five companion repos under the official-mesh org:
| Repo | Purpose |
|---|---|
official-mesh/firmware |
Meshtastic firmware fork — adds privacy modules |
official-mesh/android |
Android client for the firmware fork |
official-mesh/python |
Python CLI for the firmware fork |
official-mesh/meshbot |
LLM-powered mesh radio bot; hosts the org's CLA and Code of Conduct |
official-mesh/dualboot |
Boot selector for ESP32-S3 devices (Official Mesh firmware ↔ MeshCore) |
Meshtastic radio (optional)
↕ USB OTG serial OR BLE GATT
Android phone (Meshbot Remote)
↕ outbound TCP (default port 4404) / WireGuard
meshbot on the server ←→ inference / LLM backend
- Android 8.0+ (API 26),
compileSdk/targetSdk35 - Reachability from the phone to meshbot's peer-link port (typically over WireGuard)
- A shared-secret token configured on both sides
- Optional (for device bridging):
- USB OTG cable + Meshtastic radio with a serial adapter (FTDI, CH340, CP210x, CDC-ACM), or
- A Meshtastic radio advertising the Meshtastic BLE service UUID
- Build and install the APK (instructions below).
- Open the app. There are three tabs: Connect, Hooks, Console.
- On Connect, tap the endpoint row and set host / port / shared-secret token. Port defaults to
4404. - Tap CONNECT. The phone dials meshbot and sends a
hellocontaining the token; meshbot replies with awelcome(optionally carrying a theme colour, which tints the UI). - The foreground notification stays up for as long as the service is alive. The link auto-reconnects with exponential backoff (1 s → 30 s cap) if the TCP session drops.
- (Optional) Pick a USB or BLE radio from the device-management card to bridge a local radio into meshbot as an attached interface.
On the meshbot side, see meshbot's own docs for the corresponding peer-link configuration (shared-secret token and listen port).
| File | Purpose |
|---|---|
MainActivity.kt |
Hosts the three-tab ViewPager2, binds to ProxyService, fans out callbacks to the fragments. |
ConnectFragment.kt |
Endpoint config dialog (host/port/token), CONNECT/DISCONNECT, optional device-management card (USB / BLE picker). |
HooksFragment.kt + HooksMgr.kt |
"Hooks" tab — list / enable / disable / fire hooks on the remote meshbot. |
ConsoleFragment.kt + ConsoleMgr.kt |
"Console" tab — subscribe to remote console events, send console input, browse commands. |
ProxyService.kt |
Foreground service. Owns the single MeshbotLink and (if a device is attached) one AttachedDevicePump. Multiplexes inbound control frames to listeners (hooks + console) via CopyOnWriteArrayList. |
MeshbotLink.kt |
Outbound TCP client: connect, hello/welcome handshake, read-loop, write-loop, reconnect-with-backoff. |
AttachedDevicePump.kt |
Bridges an attached radio's serial-framed FromRadio/ToRadio stream to/from the peer-link data plane (portnum 359). Also parses enough of the config dump to populate the device-info UI. |
DeviceConnection.kt |
Common suspend interface (read, write, close, awaitReady) implemented by both transports. |
UsbSerialConnection.kt |
USB serial at 921600 baud — pure serial-framing passthrough. |
BleConnection.kt |
BLE GATT client — MTU 512, FROMNUM notifications, operations serialised through a single gate semaphore. |
ProtoReader.kt |
Hand-rolled protobuf helpers plus the peer-frame parser — no protobuf library dependency. |
Every message on the TCP link is:
0x94 0xC3 <uint16-be length> <ToRadio protobuf>
Inside the ToRadio is a MeshPacket carrying a Data payload on one of two portnums:
- 357 (control plane): JSON ops. Both directions speak the same op vocabulary:
- client→meshbot:
hello,dev_connected,dev_disconnected,list,enable,disable,fire,status_req,set_primary,primary_req,console_subscribe,console_unsubscribe,console_input,commands_req - meshbot→client:
welcome,hook,list_end,ack,err,status,primary,console_event,commands
- client→meshbot:
- 359 (data plane): raw
FromRadio/ToRadioprotobuf bytes used only when a local radio is attached. The outer peer-frame already provides framing, so theAttachedDevicePumpstrips/adds the0x94 0xC3header as it crosses the serial boundary.
One-shot token check on connect. The hello carries {"op":"hello","remote_id":…,"version":"1.0","token":…}. remote_id is a UUID generated and persisted on first run. Meshbot must respond with an op-welcome before the client will accept any further frames — any other first frame kills the session and triggers reconnect backoff.
When a user picks a USB or BLE device on the Connect tab, ProxyService.attachDevice creates an AttachedDevicePump:
- Issues
WANT_CONFIGto the radio so it re-emits its config dump. - Parses incoming frames through
DeviceInfoParserto extract node number + user identity, then emits adev_connectedcontrol op so meshbot can install a matching attached-device interface. - From then on, every
FromRadioframe is forwarded as a data-plane (portnum 359) peer-frame to meshbot, and every inbound data-plane frame from meshbot is wrapped in serial framing and written to the device.
A "Use this device as primary" toggle surfaces in the UI whenever meshbot's current primary matches this remote's attached interface; tapping it sends set_primary.
- MTU negotiation happens before service discovery; the nRF52 firmware negotiates to 247 bytes.
- FROMNUM notifications are gated — the firmware only emits them after the config handshake reaches
STATE_SEND_PACKETS. During the initial config dump (often 50+ frames) the client polls FROMRADIO after every TORADIO write; the app handles this automatically. - BLE ops are strictly serialised — Android silently drops any op issued while another is in flight. A single-token channel semaphore (
bleGate) enforces this.
Verified against firmware/src/BluetoothCommon.h:
| Characteristic | UUID |
|---|---|
| Service | 6ba1b218-15a8-461f-9fa8-5dcae273eafd |
| TORADIO | f75c76d2-129e-4dad-a1dd-7866124401e7 |
| FROMRADIO | 2c55e69e-4993-11ed-b878-0242ac120002 |
| FROMNUM | ed9da18c-a800-4f66-a670-aa7547e34453 |
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apklocal.properties must point at an Android SDK, e.g.:
sdk.dir=/opt/homebrew/share/android-commandlinetools
- One meshbot endpoint per app install (single host/port/token in
SharedPreferences). - BLE and USB transports cannot both be active at once — only one
DeviceConnectionattached at a time. - If the attached radio link drops (BLE disconnect, USB unplug), detach and re-attach from the UI; the pump does not auto-reattach the radio itself (it does auto-reconnect the peer link to meshbot).
Signed git tags and release APKs are signed by:
The Official Mesh Admin <officialmeshadmin@proton.me>
GPG fingerprint: 9A18 814D 74A6 3138 9F95 6EA0 5F8D 7A5E ED20 3343
The public key is in KEYS at the repo root, or fetch it from a keyserver:
gpg --keyserver hkps://keys.openpgp.org --recv-keys 9A18814D74A631389F956EA05F8D7A5EED203343Always verify the fingerprint matches the value above before importing.
gpg --verify meshbot-remote-1.0.apk.asc meshbot-remote-1.0.apk # release APK
git verify-tag v1.0 # signed git tag(Note: this GPG signature is independent of Android's APK signing scheme. The APK signing key proves the binary came from the same Play-store-style identity over time; the GPG signature here ties releases to the maintainer's long-term identity used across the meshbot family of repos.)
Copyright (C) 2026 The Official Mesh Admin
This program is free software: you can redistribute it and/or modify it under the terms of version 3 of the GNU Affero General Public License as published by the Free Software Foundation. See LICENSE for the full text.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.