Files
xMonitor/README.md
2026-04-15 18:25:03 +08:00

275 lines
9.1 KiB
Markdown

# xMonitor — Robot Status Monitor
A real-time web dashboard for monitoring a ROS 2 robot's motor, arm, waist, and power status.
Data is streamed from the robot controller over WebSocket and displayed in a browser.
---
## Architecture
```
Robot Controller (ROS 2) Monitor Server (PC / Server)
┌──────────────────────────┐ ┌──────────────────────────┐
│ monitor_sender.py │ WebSocket │ main_monitor.py │
│ ─────────────────────── │─────────────▶│ (FastAPI + uvicorn) │
│ Subscribes: │ /ws/robot │ │
│ • /leg/status │ │ Broadcasts to browsers │
│ • /arm/status │ │ via /ws │
│ • /waist/status │ └────────────┬─────────────┘
│ • /power/battery/status │ │ WebSocket /ws
│ • /leg/motor_status │ ▼
│ • /arm/motor_status │ ┌──────────────────────────┐
│ • /waist/motor_status │ │ Browser (any device) │
│ │ │ http://<server-ip>:8000 │
│ Sends JSON every 1 s │ └──────────────────────────┘
└──────────────────────────┘
```
---
## Requirements
### Monitor Server (PC / any host)
| Package | Purpose |
|---------|---------|
| Python ≥ 3.9 | Runtime |
| `fastapi` | Web framework |
| `uvicorn` | ASGI server |
| `websockets` | (used by sender; not needed on server) |
```bash
pip install fastapi uvicorn
```
### Robot Controller (ROS 2 node)
| Package | Purpose |
|---------|---------|
| Python ≥ 3.9 | Runtime |
| ROS 2 (Humble / Iron / …) | Middleware |
| `bodyctrl_msgs` | Custom message package |
| `websockets` | WebSocket client |
| `rclpy` | ROS 2 Python client |
```bash
pip install websockets
```
---
## Monitor Server — `main_monitor.py`
### Start
```bash
# Default: listens on all interfaces, port 8000
python main_monitor.py
# Or with uvicorn directly (supports --reload for development)
uvicorn main_monitor:app --host 0.0.0.0 --port 8000
```
### WebSocket endpoints
| Endpoint | Direction | Description |
|----------|-----------|-------------|
| `GET /` | HTTP | Returns the web dashboard HTML |
| `WS /ws` | Server → Browser | Pushes robot status to every connected browser |
| `WS /ws/robot` | Robot → Server | Receives JSON from `monitor_sender.py` |
### Web GUI
Open `http://<server-ip>:8000` in any browser.
The dashboard shows:
- **Connection status** indicator (green / red dot)
- **Last update timestamp**
- **Power panel** (two rows):
- 主电池 (Master battery): voltage (V), current (A), SOC (%)
- 副电池 (Secondary battery): voltage (V), current (A), SOC (%)
- Cards turn **orange** (warn) or **red** (alert) when voltage / SOC drops low
- **Motor status table** with columns:
`ID | Pos (rad) | Speed | Current (A) | Temp (°C) | Motor Temp | MOS Temp | Error`
- Groups: 左臂 11-14, 右臂 21-24, 腰部 31-33, 左腿 51-56, 右腿 61-66
- Temperature cells turn **yellow** when > 100 °C, **red** when > 120 °C
- Rows with errors are highlighted red; error column shows `✓` when OK
- Browser auto-reconnects every 3 s if the WebSocket drops
---
## Robot Sender — `monitor_sender.py`
Run this on the robot controller where ROS 2 is running.
### Usage
```bash
python3 monitor_sender.py --ip <server-ip> [--port <port>]
```
| Argument | Default | Description |
|----------|---------|-------------|
| `--ip` | `10.11.24.86` | IP address of the monitor server |
| `--port` | `8000` | TCP port of the monitor server |
### Examples
```bash
# Connect to server at 192.168.1.100 on default port 8000
python3 monitor_sender.py --ip 192.168.1.100
# Connect to server at 10.0.0.5 on port 9000
python3 monitor_sender.py --ip 10.0.0.5 --port 9000
```
### Subscribed ROS 2 topics
| Topic | Message Type | Description |
|-------|-------------|-------------|
| `/leg/status` | `bodyctrl_msgs/MotorStatusMsg` | Leg motor status (pos / speed / current / temp / error) |
| `/arm/status` | `bodyctrl_msgs/MotorStatusMsg` | Arm motor status |
| `/waist/status` | `bodyctrl_msgs/MotorStatusMsg` | Waist motor status |
| `/leg/motor_status` | `bodyctrl_msgs/MotorStatusMsg1` | Leg motor & MOS temperatures |
| `/arm/motor_status` | `bodyctrl_msgs/MotorStatusMsg1` | Arm motor & MOS temperatures |
| `/waist/motor_status` | `bodyctrl_msgs/MotorStatusMsg1` | Waist motor & MOS temperatures |
| `/power/battery/status` | `bodyctrl_msgs/PowerBatteryStatus` | Battery voltages, currents, SOC |
### Behaviour
- All topics are sampled once per second (1 Hz) via a ROS 2 timer, reducing network overhead.
- Data from all topics is merged into a single JSON payload and sent over WebSocket.
- Motor temperature (`motortemperature`) and MOS temperature (`mostemperature`) from `MotorStatusMsg1` are merged into the corresponding motor entry by `name` ID.
- If the WebSocket connection to the server drops, the sender automatically retries every 3 s and clears the internal queue to avoid stale data.
### JSON payload format
```json
{
"timestamp": "2026-04-05T10:00:00+00:00",
"statuses": [
{
"name": 51,
"pos": -0.2508,
"speed": -0.0051,
"current": -0.0488,
"temperature": 30.0,
"motor_temp": 28.5,
"mos_temp": 31.2,
"error": 0
}
],
"power": {
"voltage": 52.30,
"current": -2.60,
"power": 100.0,
"little_voltage": 53.10,
"little_current": -0.10,
"little_power": 94.0
}
}
```
---
## Quick-start (end-to-end)
1. **On the monitor server** (PC on the same LAN):
```bash
pip install fastapi uvicorn
python main_monitor.py
```
2. **On the robot controller** (source your ROS 2 workspace first):
```bash
source /home/ubuntu/ros2ws/install/setup.bash
python3 monitor_sender.py --ip <your-server-ip>
```
3. **Open browser**: navigate to `http://<your-server-ip>:8000`
---
## File Overview
| File | Description |
|------|-------------|
| `main_monitor.py` | FastAPI server — hosts the web GUI and relays data to browsers |
| `monitor_sender.py` | ROS 2 node — collects robot data and streams it to the server |
| `README.md` | This file |
| `scripts/start_monitor_sender.sh` | Bootstrap script for hotspot connect, environment setup, and sender launch |
| `deploy/xmonitor.env.example` | Template for `/etc/xmonitor/xmonitor.env` |
| `deploy/xmonitor-sender.service` | `systemd` service template for robot-side autostart |
---
## Robot Sender Autostart (Ubuntu + systemd)
For field deployment on the robot controller, this repo now includes:
- `scripts/start_monitor_sender.sh`
- `deploy/xmonitor.env.example`
- `deploy/xmonitor-sender.service`
The design stays intentionally small:
- the startup script keeps trying to join your phone hotspot with `nmcli`
- it waits until the PC hotspot IP is reachable
- it sources the ROS setup and `.venv`
- it launches `python3 monitor_sender.py --ip <server-ip> --port <port>`
- `systemd` restarts the process after crashes, while logs go to both `journalctl` and `~/xMonitor/logs/`
### Install on the Ubuntu robot device
Assuming the repo is deployed at `/home/ubuntu/xMonitor`:
```bash
cd /home/ubuntu/xMonitor
sudo install -d -m 700 /etc/xmonitor
sudo cp deploy/xmonitor.env.example /etc/xmonitor/xmonitor.env
sudo chmod 600 /etc/xmonitor/xmonitor.env
sudoedit /etc/xmonitor/xmonitor.env
chmod +x scripts/start_monitor_sender.sh
sudo cp deploy/xmonitor-sender.service /etc/systemd/system/xmonitor-sender.service
sudo systemctl daemon-reload
sudo systemctl enable --now xmonitor-sender.service
```
If you deploy the repo somewhere other than `/home/ubuntu/xMonitor`, update these together:
- `User=` in `deploy/xmonitor-sender.service`
- `WorkingDirectory=` in `deploy/xmonitor-sender.service`
- `ExecStart=` in `deploy/xmonitor-sender.service`
- `APP_DIR=` and `RUN_USER=` in `/etc/xmonitor/xmonitor.env`
### Runtime checks
```bash
# Run the bootstrap script manually for troubleshooting
bash /home/ubuntu/xMonitor/scripts/start_monitor_sender.sh
# Service state
systemctl status xmonitor-sender.service
# Live logs from systemd
journalctl -u xmonitor-sender.service -f
# Per-run log files
ls /home/ubuntu/xMonitor/logs
```
When run manually, the script now auto-loads `/etc/xmonitor/xmonitor.env` if that file exists.
### Behavior
- Hotspot connect retry: every 5 seconds until the target SSID is connected
- Server reachability retry: every 5 seconds until `SERVER_IP` answers `ping`
- Python process crash: `systemd` restarts after 5 seconds
- WebSocket drop after startup: handled by `monitor_sender.py`, which already retries every 3 seconds
- Per-run log files are pruned automatically; default retention is the latest 20 files via `LOG_RETENTION_COUNT`