# OmniBot Android controller Kotlin app that drives an OmniBot-style robot over a ROSBridge WebSocket. The Gradle project is this repository root (`settings.gradle.kts` includes `:app`). There is no `android_app/` directory. Package `com.varunvaidhiya.robotcontrol`. Min SDK 24, compile and target SDK 35.

Apache-2.0 Android min SDK 24 Kotlin 2.1.0 Android Gradle Plugin 8.13.2

The license badge matches `LICENSE` (Apache-2.0). CI runs `./gradlew assembleDebug test` and uploads `app-debug.apk` on each run (see `.github/workflows/android-ci.yml`). ## What is real in this build | What you see | What the code does | |---|---| | Settings IP and port | Saved in DataStore. On launch `MainViewModel` reads them and connects; SAVE CONFIG also calls `connectToRobot`, which opens `ws://:` (cleartext, not TLS). | | Connection dot | Green / amber / red from the ROSBridge socket state. | | Home battery and Pi cards | When ROSBridge is connected and the robot (or `tools/rosbridge_stub.py`) publishes `/battery_state` and `/diagnostics`, those cards show **live** values. Workstation and model load stay **demo** until wired. The banner labels LIVE vs DEMO. | | Home joint readout and empty 3D pane | Placeholders. `robot.glb` is not in this repo, and this screen does not load one. | | Dashboard camera | MJPEG from `http://:8080/stream?topic=/camera/front/image_raw` only after the socket reports connected. Badge **CAMERA OFFLINE** when disconnected, **CONNECTING…** until the first JPEG, then **● LIVE**. | | Map → 2D SLAM | Renders `/map` (occupancy grid) with robot pose from `/odom` (meters → map pixels). Placeholder until the first grid arrives. | | AI command box | Publishes the typed text to `/agent/goal` (std_msgs/String) while connected, plus existing `/ai/command` / `/mission/command` routing. | | Dashboard chart and odometry | Values from `/odom` after a connection. Zeros until then. The motor tiles are labelled DEMO and are not wheel telemetry. | | Map, point cloud, 3D robot viewer | Views subscribe to `/map`, `/camera/depth/points`, `/odom`, and `/arm/joint_states`. The 3D tab looks for `robot.glb` in assets and shows a missing-model state when it is absent. | | AI chat | Sends text on `/agent/goal`, `/ai/command`, or `/mission/command` only while the socket is connected. The face animation (idle / processing / speaking) is a local timer, not a robot reply. | | Controls | Joystick and arm sliders publish `/cmd_vel/teleop`, `/control_mode`, and `/arm/joint_commands/teleop` when connected. | | Observability | HTTP clients for Prometheus, Loki, Alertmanager, and W&B, using the GPU IP and W&B fields from Settings. Empty when those services are not reachable. | | OTA | Calls ROS services `/ota/check`, `/ota/apply_workspace`, `/ota/apply_models`, and `/ota/rollback_workspace` when connected. | ROS topic names live in `app/src/main/kotlin/com/varunvaidhiya/robotcontrol/utils/Constants.kt`. ## Roadmap Not in this repository, and not claimed as working here: - TLS (`wss://`) for ROSBridge. The manifest allows cleartext traffic. - A checked-in `robot.glb`, URDF, or `tools/urdf_to_glb.py`. - Robot-side nodes (`rosbridge`, `web_video_server`, Nav2, camera, arm, OTA, Loki). This app only speaks to them. - Updated emulator screenshots after live-metrics / camera UI (see `docs/OHH-38-human-todos.md`, `docs/OHH-61-human-todos.md`). ## Screenshots API 35 emulator, no robot. The socket shows online because a WebSocket listener on the host accepted `ws://10.0.2.2:9090`. Home metrics are labelled demo data. The camera has no frame. ![Home on an emulator: SYS ONLINE, DEMO DATA banner on the simulated metrics](docs/screenshots/home.png) ![Dashboard on an emulator: NO FEED, motor tiles labelled demo](docs/screenshots/dashboard.png) ![Settings on an emulator: robot IP 10.0.2.2 and ROSBridge port 9090](docs/screenshots/settings.png) ## Navigation `MainActivity` hosts `res/navigation/mobile_navigation.xml`. Bottom bar (six items; `OmniBottomNavigationView` raises the Material limit of five): - Home - Dashboard - Map (ViewPager: 2D SLAM, point cloud, 3D robot) - AI - Controls - OTA The top bar, not the bottom bar, opens Settings and Observability. `ui/logs/LogsFragment.kt` is not a navigation destination. The logs page that is on screen is `ui/observability/logs/`. ## Repository layout ``` . ├── .github/workflows/android-ci.yml ├── app/ │ ├── build.gradle.kts │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── kotlin/com/varunvaidhiya/robotcontrol/ │ │ │ ├── MainActivity.kt │ │ │ ├── RobotApplication.kt │ │ │ ├── di/NetworkModule.kt │ │ │ ├── data/models/ │ │ │ ├── data/preferences/AppPreferences.kt │ │ │ ├── data/remote/ # Prometheus, Loki, Alertmanager, W&B │ │ │ ├── data/repository/ # RobotRepository, ObservabilityRepository │ │ │ ├── network/ # ROSBridgeManager │ │ │ ├── utils/Constants.kt │ │ │ └── ui/ # home, dashboard, mapping, slam, viewer, │ │ │ # ai, controls, observability, ota, settings, views │ │ └── res/ # layout, drawable, navigation, values │ ├── androidTest/ # Stub smoke tests (@Ignore until host stub runs) │ └── test/ # JVM unit tests ├── build.gradle.kts ├── gradle/wrapper/ ├── gradlew ├── settings.gradle.kts # rootProject.name = RobotControl ├── tools/ │ ├── rosbridge_stub.py # WebSocket stub for emulator dev │ └── requirements-stub.txt ├── docs/OHH-38-human-todos.md # screenshots, recording, hardware checks ├── robot_app_prompt.md # original build notes, not a status report └── README.md ``` There is no `app/src/main/assets/` directory in git. ## Build | Tool | Version used here | |---|---| | JDK | 17 (CI). JDK 21 also runs this Android Gradle Plugin. | | Android Gradle Plugin | 8.13.2 | | Gradle | 8.13 (wrapper) | | Kotlin | 2.1.0 | | Compile / target SDK | 35 | | Min SDK | 24 | ```bash # from this repository root, with ANDROID_HOME or local.properties sdk.dir set ./gradlew assembleDebug test ./gradlew installDebug ``` `local.properties` is gitignored. Debug APK output: `app/build/outputs/apk/debug/app-debug.apk`. On a robot network, run your own `rosbridge` on port 9090 and, for the camera, `web_video_server` on port 8080. Those processes are not in this repo. The default address in the app is `192.168.1.100:9090`. From an Android emulator, the host machine is `10.0.2.2`. ### Local ROSBridge stub (not a robot) For emulator smoke tests without hardware: ```bash pip install -r tools/requirements-stub.txt python tools/rosbridge_stub.py --port 9090 ``` The stub publishes synthetic `/battery_state`, `/diagnostics`, `/odom`, and `/map`, logs inbound `/agent/goal` messages, and serves MJPEG on port **8080** (`/stream?topic=…`). It does not replace a real robot stack. See `docs/OHH-61-human-todos.md` for emulator checks. ## License [Apache License 2.0](LICENSE). No third-party source is vendored in this repository (there is no Meta XR SDK tree or other copied SDK). Dependencies are declared in `app/build.gradle.kts` and keep their own licenses. The Gradle wrapper jar under `gradle/wrapper/` stays under the Gradle wrapper's license.