# Motor Test Joystick (Issue #513) Terminal-based interactive joystick for bench testing SaltyBot motors via Termux. ## Quick Start On phone (Termux): ```bash # With ROS2 (default, requires ros_core running on Jetson) python3 ~/saltylab-firmware/phone/motor_test_joystick.py # With WebSocket (if ROS2 unavailable) python3 ~/saltylab-firmware/phone/motor_test_joystick.py --backend websocket --host ``` ## Controls | Key | Action | |-----|--------| | **W** / **↑** | Forward (linear +X) | | **S** / **↓** | Reverse (linear -X) | | **A** / **←** | Turn left (angular +Z) | | **D** / **→** | Turn right (angular -Z) | | **SPACE** | E-stop toggle (hold disables motors) | | **R** | Reset velocities to zero | | **Q** | Quit | ## Features ### Real-Time Feedback - Live velocity displays (linear X, angular Z) - Velocity bar graphs with ● indicator - Current input state (before clamping) - Timeout warning (>500ms since last command) - Status message line ### Safety Features - **E-stop button** (spacebar): Instantly zeros velocity, toggle on/off - **Timeout safety**: 500ms without command → sends zero velocity - **Velocity ramping**: Input decays exponentially (95% per frame) - **Conservative defaults**: 0.1 m/s linear, 0.3 rad/s angular - **Graceful fallback**: WebSocket if ROS2 unavailable ### Dual Backend Support - **ROS2 (primary)**: Publishes directly to `/cmd_vel` topic - **WebSocket (fallback)**: JSON messages to Jetson bridge (port 9090) ## Usage Examples ### Standard ROS2 mode (Jetson has ros_core) ```bash python3 motor_test_joystick.py ``` Sends Twist messages to `/cmd_vel` at ~20Hz ### WebSocket mode (fallback if no ROS2) ```bash python3 motor_test_joystick.py --backend websocket --host 192.168.1.100 ``` Sends JSON: `{"type": "twist", "linear_x": 0.05, "angular_z": 0.0, "timestamp": 1234567890}` ### Custom velocity limits ```bash python3 motor_test_joystick.py --linear-max 0.5 --angular-max 1.0 ``` Max forward: 0.5 m/s, max turn: 1.0 rad/s ### Combine options ```bash python3 motor_test_joystick.py \ --backend websocket \ --host saltybot.local \ --linear-max 0.2 \ --angular-max 0.5 ``` ## Architecture ### MotorTestController Main state machine: - Manages velocity state (linear_x, angular_z) - Handles e-stop state - Enforces 500ms timeout - Clamps velocities to max limits - Sends commands to backend ### Backend Options **ROS2Backend**: Direct Twist publisher - Requires `geometry_msgs` / `rclpy` - Topic: `/cmd_vel` - Spin thread for ros2.spin() **WebSocketBackend**: JSON over TCP socket - No ROS2 dependencies - Connects to Jetson:9090 (configurable) - Fallback if ROS2 unavailable ### Curses UI - Non-blocking input (getch timeout) - 20Hz refresh rate - Color-coded status (green=ok, red=estop/timeout, yellow=bars) - Real-time velocity bars - Exponential input decay (95% per frame) ## Terminal Requirements - **Size**: Minimum 80×25 characters (larger is better for full feedback) - **Colors**: 256-color support (curses.init_pair) - **Non-blocking I/O**: ncurses.nodelay() - **Unicode**: ● and 🛑 symbols (optional, falls back to ASCII) ### Test in Termux ```bash stty size # Should show >= 25 lines echo $TERM # Should be xterm-256color or similar ``` ## Performance | Metric | Value | Notes | |--------|-------|-------| | **UI Refresh** | 20 Hz | Non-blocking, timeout-based | | **Command Rate** | 20 Hz | Updated per frame | | **Timeout Safety** | 500ms | Zero velocity if no input | | **Input Decay** | 95% per frame | Smooth ramp-down | | **Max Linear** | 0.1 m/s (default) | Conservative for bench testing | | **Max Angular** | 0.3 rad/s (default) | ~17°/s rotation | ## Troubleshooting ### "ROS2 module not found" → Run with `--backend websocket` instead ### "Connection refused" (WebSocket mode) → Check Jetson IP with `--host `, verify bridge listening on :9090 ### Motors not responding 1. Check e-stop status (should show "✓ Inactive") 2. Verify timeout warning (>500ms = zero velocity sent) 3. Check Jetson `/cmd_vel` subscription: `ros2 topic echo /cmd_vel` 4. Verify network connectivity (WiFi/tethering) ### Terminal artifacts / display issues - Try `reset` or `stty sane` in Termux - Increase terminal size (pinch-zoom) - Use `--backend websocket` (simpler UI fallback) ## Safety Checklist Before Testing - [ ] Phone connected to Jetson (WiFi or USB tether) - [ ] Motors disconnected or isolated (bench testing mode) - [ ] E-stop accessible (spacebar, always reachable) - [ ] Terminal window visible (no hide/scroll) - [ ] Max velocities appropriate (start conservative: 0.1/0.3) - [ ] Kill switch ready (Ctrl+C, or `ros2 topic pub --once /cmd_vel ...`) ## Future Enhancements - [ ] Gamepad/joystick input (evdev) instead of keyboard - [ ] Configurable button mappings - [ ] Velocity profile presets (slow/medium/fast) - [ ] Motor current feedback from motor driver - [ ] Telemetry logging (CSV) for bench analysis - [ ] Multi-motor independent control ## Related Issues - **#420** — Termux bootstrap & Android phone node - **#508** — Face LCD animations (separate system) - **#512** — Autonomous arming (uses /cmd_vel via motor bridge) ## References - [ROS2 Humble - geometry_msgs/Twist](https://docs.ros.org/en/humble/Concepts/Intermediate/About-Different-Distributions.html) - [Termux - Python environment](https://wiki.termux.com/wiki/Python) - [ncurses - Curses module](https://docs.python.org/3/library/curses.html)