A modern, real-time web application for visualizing ROS2 robots and sensor data
Built with Next.js 14, React 18, and Three.js for high-performance 3D visualization
Features β’ Installation β’ Usage β’ Documentation β’ Contributing
- Features
- Demo
- Prerequisites
- Installation
- Quick Start
- Usage
- Architecture
- ROS2 Integration
- Customization
- Testing
- Troubleshooting
- Production Build
- Contributing
- License
- Acknowledgments
- Support
- Real-time ROS2 Connection: Connect to ROS2 via rosbridge_server WebSocket
- Auto-reconnection: Automatic reconnection on connection loss
- Topic Management: Browse, search, and subscribe to ROS2 topics
- Live Data Monitoring: View real-time topic data in JSON format
- URDF 3D Viewer: Visualize robot models from
/robot_descriptiontopic - Live Robot Motion: Robot model automatically updates with
/joint_statestopic - Point Cloud Viewer: Real-time point cloud visualization from depth cameras
- Interactive 3D Controls: Pan, zoom, and rotate with mouse controls
- Color Mapping: Depth-based or RGB coloring for point clouds
- Modern Dark Theme: Beautiful gradient-based dark interface
- Responsive Design: Works on desktop, tablet, and mobile
- Real-time Updates: Live connection status and message counts
- Search & Filter: Quickly find topics with instant search
- Draggable Widgets: Customizable dashboard layout with drag-and-drop
- Persistent Configuration: Layout and settings saved in browser localStorage
Note: Add screenshots or GIF demonstrations of MITI in action here.
πΈ Click to view screenshots
Screenshots coming soon!
- Dashboard with multiple widgets
- URDF robot visualization
- Point cloud viewer
- Topic browser
Before running MITI, ensure you have the following installed:
- ROS2 Distribution: Humble Hawksbill or later
- rosbridge_suite: For WebSocket communication
# Install ROS2 (Ubuntu)
### Option 1: Clone from GitHub
```bash
# Clone the repository
git clone https://github.com/yourusername/miti.git
cd mitiDownload the latest release from the Releases page.
Using Bun (recommended):
bun installOr using npm:
npm installNote: This project uses Bun as the primary package manager and runtime. While npm/yarn will work, Bun provides significantly better performance and is recommended for development.
Install Bun:
curl -fsSL https://bun.sh/install | bash
git clone https://github.com/thongpanchang/miti.git
cd miti- Install dependencies
Using Bun (recommended):
bun installOr using npm:
npm installNote: This project uses Bun as the primary package manager and runtime. While npm/yarn will work, Bun provides better performance and is recommended.
- Configure rosbridge connection (optional)
You can configure the rosbridge server URL in two ways:
Option 1: Using environment variables (for default connection)
Copy the example environment file and edit it:
cp .env.example .env.localEdit .env.local and set your rosbridge URL:
NEXT_PUBLIC_ROSBRIDGE_URL=ws://192.168.10.27:9090Option 2: Using the UI settings (recommended for easy switching)
Once the app is running, click the settings icon (βοΈ) next to the connection status to change the rosbridge URL without editing files. The URL is saved in your browser's localStorage and persists across sessions.
Examples:
- Local development:
ws://localhost:9090 - Remote rosbidge:
ws://192.168.10.27:9090 - Docker container:
ws://ros-bridge:9090(network=host)
First, source your ROS2 workspace and start rosbridge:
# Source ROS2
source /opt/ros/humble/setup.bash
# Start rosbridge_server
ros2 launch rosbridge_server rosbridge_websocket_launch.xmlThe rosbridge server will start on ws://localhost:9090 by default.
In a new terminal, start the Next.js development server:
Using Bun (recommended):
bun devOr using npm:
npm run devBuilding for production:
bun run build # Build optimized production bundle bun start # Start production server
Navigate to http://localhost:3000
You should see:
- β Connection status showing "Connected"
- π List of available ROS2 topics
- π¨ 3D visualization panels ready
miti/
βββ src/ # Source directory (Next.js 14 App Router)
β βββ app/ # Next.js App Router
β β βββ globals.css # Global styles
β β βββ layout.tsx # Root layout
β β βββ page.tsx # Home page
β β
β βββ components/ # React components (feature-based organization)
β β βββ features/ # Feature-specific components
β β βββ connection/ # Connection management
β β β βββ ConnectionStatus.tsx
β β β βββ ConnectionSettings.tsx
β β βββ dashboard/ # Main dashboard
β β β βββ Dashboard.tsx
β β βββ layout/ # Layout and grid system
β β β βββ DraggableGridLayout.tsx
β β β βββ WidgetContainer.tsx
β β β βββ AddWidgetButton.tsx
β β β βββ LayoutConfig.tsx
β β βββ topic-viewer/ # Topic browsing and monitoring
β β β βββ TopicList.tsx
β β β βββ TopicCard.tsx
β β β βββ TopicDataDisplay.tsx
β β βββ visualization/ # 3D visualization components
β β βββ camera/ # Camera feed viewer
β β β βββ CameraViewer.tsx
β β βββ pointcloud/ # Point cloud visualization
β β β βββ PointCloudViewer.tsx
β β β βββ PointCloudRenderer.tsx
β β βββ tf/ # TF frame visualization
β β β βββ TFVisualizer.tsx
β β βββ urdf/ # URDF robot model viewer
β β β βββ URDFViewer.tsx
β β β βββ URDFModel.tsx
β β β βββ URDFSettings.tsx
β β β βββ URDFSourceSelector.tsx
β β β βββ URDFLoadStatus.tsx
β β β βββ Scene3D.tsx
β β βββ shared/ # Shared visualization components
β β β βββ ViewerControls.tsx
β β βββ hooks/ # Visualization-specific hooks
β β βββ useUrdfUrlLoader.ts
β β
β βββ hooks/ # Global React hooks
β β βββ useRosbridge.ts # ROS connection management
β β βββ useTopic.ts # Topic subscription
β β βββ useTopicList.ts # Topic discovery
β β βββ useTF.ts # TF frame handling
β β βββ useLayoutConfig.ts # Widget layout management
β β βββ useLocalStorage.ts # Local storage utilities
β β βββ useUrdfConfig.ts # URDF configuration state
β β
β βββ lib/ # Core libraries
β β βββ rosbridge/ # rosbridge client
β β β βββ client.ts # WebSocket client
β β β βββ types.ts # TypeScript types
β β β βββ messages.ts # Message builders
β β βββ parsers/ # Data parsers
β β βββ image-parser.ts
β β βββ pointcloud-parser.ts
β β βββ urdf-parser/ # URDF loading utilities
β β βββ urdf-url-loader.ts
β β βββ urdf-loader-helper.ts
β β
β βββ styles/ # Centralized style modules
β β βββ index.ts # Main style exports
β β βββ common.styles.ts # Reusable UI patterns
β β βββ widget.styles.ts # Widget container styles
β β βββ topic.styles.ts # Topic viewer styles
β β βββ connection.styles.ts # Connection UI styles
β β βββ dashboard.styles.ts # Dashboard layout styles
β β βββ layout-config.styles.ts # Layout config styles
β β βββ visualization.styles.ts # Visualization styles
β β
β βββ types/ # TypeScript definitions
β βββ ros-messages.d.ts # ROS message types
β βββ tf-messages.d.ts # TF frame types
β βββ urdf-config.ts # URDF configuration
β βββ urdf-loader.d.ts # URDF loader types
β βββ widget.ts # Widget configuration
β
βββ public/ # Static assets
β βββ main_logo.svg
β βββ ...
β
βββ bunfig.toml # Bun configuration
βββ next.config.js # Next.js configuration
βββ tailwind.config.ts # Tailwind CSS configuration
βββ tsconfig.json # TypeScript configuration
βββ package.json # Dependencies and scripts
βββ LICENSE # MIT License
βββ CONTRIBUTING.md # Contribution guidelines
Components are organized by feature rather than by type, making it easier to:
- Locate related components
- Understand feature boundaries
- Scale the application
- Maintain and refactor code
All component styles are extracted into TypeScript constant modules in src/styles/:
- Consistency: Single source of truth for design patterns
- Maintainability: Easy to update styles across components
- Type Safety: Full TypeScript support with intellisense
- Reusability: Common patterns defined once in
common.styles.ts - Organization: Feature-specific styles in separate modules
Example usage:
import { visualizationStyles, cn } from '@/styles';
<div className={visualizationStyles.camera.container}>
<button className={cn(
commonStyles.button.primary,
isActive && commonStyles.button.active
)}>
Click me
</button>
</div>The project uses TypeScript path aliases for clean imports:
@/components/*βsrc/components/*@/hooks/*βsrc/hooks/*@/lib/*βsrc/lib/*@/styles/*βsrc/styles/*@/types/*βsrc/types/*@/utils/*β `src/utils/* βββ utils/ # Utility functions β βββ pointcloud-parser.ts β βββ urdf-loader-helper.ts βββ types/ # TypeScript definitions β βββ ros-messages.d.ts # ROS message types βββ public/ # Static assets
### Technology Stack
- **Frontend Framework**: Next.js 14 (App Router with `src/` directory)
- **Language**: TypeScript 5.9+
- **Runtime**: Bun (recommended) / Node.js 20+
- **Styling**: Tailwind CSS 3.4+ with centralized style modules
- **3D Graphics**: Three.js, React Three Fiber (@react-three/fiber), Drei (@react-three/drei)
- **State Management**: React hooks + localStorage
- **Grid Layout**: react-grid-layout (for draggable widget system)
- **Icons**: Lucide React
- **URDF Parsing**: urdf-loader (with custom URL loader extensions)
- **ROS Integration**: Custom rosbridge WebSocket client
## π‘ ROS2 Integration
### Supported Message Types
MITI currently supports:
- `std_msgs/String` - For URDF robot descriptions
- `sensor_msgs/JointState` - For robot joint positions (motion monitoring)
- `sensor_msgs/PointCloud2` - For point cloud data
- All standard ROS2 message types (view-only)
### URDF Loading and Motion Monitoring
MITI supports loading URDF robot models and monitoring their motion in real-time.
#### Joint States Motion Monitoring
Once a URDF model is loaded, MITI automatically subscribes to the `/joint_states` topic to animate the robot:
```bash
# The robot will automatically move as joint states are published
ros2 topic pub /joint_states sensor_msgs/msg/JointState "{name: ['joint1', 'joint2'], position: [0.5, 1.0]}"
Features:
- Real-time joint position updates
- Automatic joint name mapping
- Supports all joint types (revolute, prismatic, continuous, etc.)
- No configuration required - just publish to
/joint_states
MITI supports multiple ways to load URDF robot descriptions:
Subscribe to /robot_description topic or any custom topic:
# Publish URDF to ROS topic
ros2 topic pub /robot_description std_msgs/msg/String "data: '$(cat robot.urdf)'" --onceIn MITI:
- Ensure "ROS Topic" mode is selected
- Enter your topic name (dehooks/useTopic';
function MyComponent({ client }) { const { data, lastUpdate } = useTopic( client, '/my_topic', 'std_msgs/String' );
return
### Creating Custom Widgets
To add a new widget to the dashboard:
**Option 1: Environment Variable** (for default connection)
Edit `.env.local`:
```env
NEXT_PUBLIC_ROSBRIDGE_URL=ws://your-robot-ip:9090
Option 2: UI Settings (recommended)
Click the settings icon (βοΈ) next to the connection status to change the URL dynamically. Settings are persisted in browser localStorage.
The project uses a centralized style system for consistency and maintainability.
Modifying Existing Styles:
Edit the appropriate style module in src/styles/:
// src/styles/visualization.styles.ts
export const visualizationStyles = {
camera: {
container: 'relative w-full h-full bg-gray-950 rounded-lg', // Modify here
// ...
}
};Adding New Style Patterns:
Add to src/styles/common.styles.ts for reusable patterns:
export const commonStyles = {
button: {
custom: 'px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded-lg',
},
};Theme Configuration: Edit global theme settings in:
src/app/globals.css- Global CSS variables and base stylestailwind.config.ts- Tailwind theme extensions and color palette
- Create component in
src/components/features/visualization/ - Add feature-specific styles to
src/styles/visualization.styles.ts - Use hooks from
src/hooks/to subscribe to ROS topics - Register in widget system via
src/types/widget.ts - Add to
WidgetContainer.tsxfor dashboard integration
Example structure:
src/components/features/visualization/
βββ my-viz/
βββ MyVizViewer.tsx # Main component
βββ MyVizRenderer.tsx # Rendering logic
βββ MyVizControls.tsx # User controls
</div>
)
3. Enter URDF URL: http://192.168.10.27:8000/robot.urdf
4. Enter Mesh Base URL: http://192.168.10.27:8000
5. Click "Load URDF"
Example: Using Python HTTP Server
# In your robot description package
cd /path/to/robot_description
python3 -m http.server 8000 --bind 0.0.0.0Example: Using Python HTTP Server with CORS
# server.py
from http.server import HTTPServer, SimpleHTTPRequestHandler
class CORSRequestHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
self.send_header('Access-Control-Allow-Headers', '*')
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
if __name__ == '__main__':
print('Starting server on http://0.0.0.0:8000')
HTTPServer(('0.0.0.0', 8000), CORSRequestHandler).serve_forever()Run with: python3 server.py
URDF files often reference meshes using ROS package paths. MITI automatically converts these to HTTP URLs:
Input: package://robot_description/meshes/base_link.stl
Base: http://192.168.10.27:8000
Output: http://192.168.10.27:8000/meshes/base_link.stl
For Complex Setups with Multiple Packages:
You can configure package mappings for robots that use multiple packages:
{
"robot_description": "http://192.168.10.27:8000",
"gripper_description": "http://192.168.10.27:8001",
"sensor_description": "http://192.168.10.27:8002"
}Example resolution with package mapping:
Input: package://gripper_description/models/gripper.dae
Mapping: { "gripper_description": "http://192.168.10.27:8001" }
Output: http://192.168.10.27:8001/models/gripper.dae
If loading URDF from a different origin, you must configure CORS on your server. The Python example above shows how to enable CORS.
For NGINX:
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
add_header 'Access-Control-Allow-Headers' '*';
}For Apache:
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, OPTIONS"
Header set Access-Control-Allow-Headers "*"import { useTopic } from '@/app/hooks/useTopic';
function MyComponent({ client }) {
const { data, lastUpdate } = useTopic(
client,
'/my_topic',
'std_msgs/String'
);
return <div>{data?.data}</div>;
}Build and deploy MITI for production:
# Build optimized production bundle
bun run build
# Start production server
bun start
# Or using npm
npm run build
npm startThe production build will:
- Optimize and minify all code
- Generate static assets
- Enable production-grade performance
- Tree-shake unused code
- Optimize images and assets
Build Output:
- Next.js generates optimized bundles in
.next/ - Static assets are served from
.next/static/ - Server-side rendering ready
Environment Variables for Production:
Create .env.production:
NEXT_PUBLIC_ROSBRIDGE_URL=ws://your-production-robot:9090Docker Deployment (optional):
FROM oven/bun:1 as base
WORKDIR /app
# Install dependencies
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
# Copy source
COPY . .
# Build
RUN bun run build
# Start
EXPOSE 3000
CMD ["bun", "start"]mizing the Theme
Edit `app/globals.css` or Tailwind configuration in `tailwind.config.ts`
### Adding New Visualizations
1. Create a new component in `app/components/Visualization/`
2. Use the `useTopic` hook to subscribe to your topic
3. Add it to the Dashboard grid layout
## π§ͺ Testing
### Manual Testing Checklist
- [ ] rosbridge_server connection works
- [ ] Topics list loads correctly
- [ ] Can subscribe/unsubscribe to topics
- [ ] Topic data displays in real-time
- [ ] URDF viewer loads robot models
- [ ] Robot joints move with /joint_states topic
- [ ] Point cloud renders correctly
- [ ] Reconnection works after disconnect
- [ ] Search and filter topics works
- [ ] Responsive on mobile devices
- [ ] No console errors
### Publishing Test Data
```bash
# Publish test string
ros2 topic pub /test_topic std_msgs/msg/String "data: 'Hello MITI'"
# Publish robot description (example)
ros2 topic pub /robot_description std_msgs/msg/String "data: '$(cat robot.urdf)'"
# Publish joint states for robot motion
ros2 topic pub /joint_states sensor_msgs/msg/JointState "{header: {stamp: {sec: 0, nanosec: 0}, frame_id: ''}, name: ['joint1', 'joint2'], position: [0.5, 1.0], velocity: [], effort: []}"Problem: Cannot connect to rosbridge_server
Solutions:
- Ensure rosbridge_server is running:
ros2 node list | grep rosbridge - Check the WebSocket URL in
.env.local - Verify firewall settings allow WebSocket connections
- Check browser console for detailed error messages
Problem: Topics list is empty
Solutions:
- Verify ROS2 nodes are running:
ros2 node list - Check if topics exist:
ros2 topic list - Try refreshing the topics list (click refresh button)
- Ensure rosbridge_server is properly connected to ROS2
Problem: Point cloud viewer shows "No data"
Solutions:
- Verify topic name matches your camera:
/camera/depth/points - Check message type is
sensor_msgs/PointCloud2 - Ensure point cloud data is being published:
ros2 topic hz /camera/depth/points - Check browser console for parsing errors
Problem: Cannot load URDF from URL
Solutions:
-
CORS Error: Your server needs to allow cross-origin requests
- Use the Python CORS server example provided above
- Configure your web server (NGINX, Apache) to send CORS headers
- For development, you can use a CORS proxy
-
Network Error:
- Verify the URL is correct and accessible
- Test URL in browser:
curl http://192.168.10.27:8000/robot.urdf - Check if server is running and reachable
- Verify firewall/network settings
-
Mesh Loading Failures:
- Ensure Mesh Base URL is set correctly
- Check that mesh files are accessible from the base URL
- Verify package:// paths are being resolved correctly
- Check browser console for specific mesh file errors
-
URDF Parse Error:
- Verify URDF file is valid XML
- Check for syntax errors in URDF file
- Ensure file is complete (not truncated)
Testing URDF URL Loading:
# 1. Create test directory
mkdir -p /tmp/urdf_test/meshes
cd /tmp/urdf_test
# 2. Create simple URDF (save as robot.urdf)
cat > robot.urdf << 'EOF'
<?xml version="1.0"?>
<robot name="test_robot">
<link name="base_link">
<visual>
<geometry>
<box size="1 1 1"/>
</geometry>
</visual>
</link>
</robot>
EOF
# 3. Start server with CORS
python3 -c "
from http.server import HTTPServer, SimpleHTTPRequestHandler
class CORSHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
super().end_headers()
HTTPServer(('0.0.0.0', 8000), CORSHandler).serve_forever()
"
# 4. In MITI:
# - Switch to URL mode
# - URDF URL: http://localhost:8000/robot.urdf
# - Click Load URDFProblem: Build fails with dependency errors
Solutions:
# Clear cache and reinstall
rm -rf node_modules .next
bun install
# or
npm install
# If using Bun and it crashes, use npm instead
npm run dev# Build for production
bun run build
# or
npm run build
# Start production server
bun start
# or
npm startWe love contributions! MITI is a community-driven project, and we welcome contributions of all kinds:
- π Bug reports - Found a bug? Open an issue
- π‘ Feature requests - Have an idea? Start a discussion
- π Documentation - Improve our docs
- π§ Code contributions - Submit a pull request
- Fork the repository
- Clone your fork: `git clone MIT License - see the LICENSE file for details.
MIT License
Copyright (c) 2026 MITI Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
MITI stands on the shoulders of giants. We are grateful to:
- ROS2 - Robot Operating System community and maintainers
- rosbridge_suite - WebSocket bridge for ROS
- Three.js - 3D graphics library
- React Three Fiber - React renderer for Three.js
- Next.js - React framework by Vercel
- Tailwind CSS - Utility-first CSS framework
- Lucide - Beautiful icon set
- Bun - Fast JavaScript runtime
- urdf-loader - URDF parsing for Three.js
Special thanks to all contributors who have helped make MITI better!
- π Documentation: Start with this README and check our wiki (if available)
- π Bug Reports: Open an issue with detailed reproduction steps
- π‘ Feature Requests: Start a discussion to propose new ideas
- β Questions: Check existing issues or open a new one
- GitHub Issues: For bug reports and feature requests
- GitHub Discussions: For questions and community support
- Pull Requests: For code contributions
- β Star this repository to show support
- ποΈ Watch for updates and new releases
- π΄ Fork to create your own customized version
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- ROS2 and the Robot Operating System community
- rosbridge_suite developers
- Three.js and React Three Fiber teams
- Next.js and Vercel teams
For questions or support, please open an issue on GitHub.
Made with β€οΈ for the ROS2 community