fix: 自启动
This commit is contained in:
8
.claude/settings.local.json
Normal file
8
.claude/settings.local.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(find deploy:*)",
|
||||||
|
"Bash(ls -R deploy/ scripts/)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,3 +16,6 @@ env/
|
|||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Runtime logs
|
||||||
|
logs/
|
||||||
|
|
||||||
|
|||||||
162
Plan.md
Normal file
162
Plan.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
自启方案:monitor_sender.py 通过手机热点自动启动
|
||||||
|
|
||||||
|
Context
|
||||||
|
|
||||||
|
monitor_sender.py 是一个 ROS2 节点,运行在机器人控制器上(Ubuntu, 用户名
|
||||||
|
ubuntu),订阅机器人状态话题并通过 WebSocket 发送到 PC
|
||||||
|
端监控服务器。设备在真实世界运行,需要开机后:
|
||||||
|
1. 自动连接手机热点 WiFi
|
||||||
|
2. 等待网络就绪后启动 monitor_sender.py(通过热点向 PC 的固定 IP 发送数据)
|
||||||
|
3. 崩溃后自动重启并记录崩溃原因
|
||||||
|
|
||||||
|
方案:systemd 服务 + 启动脚本(两个文件搞定)
|
||||||
|
|
||||||
|
文件 1:启动脚本 ~/xMonitor/start_monitor.sh
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
# ========== 配置区(按实际修改) ==========
|
||||||
|
HOTSPOT_SSID="你的热点名称" # ← 稍后填
|
||||||
|
HOTSPOT_PASS="你的热点密码" # ← 稍后填
|
||||||
|
SERVER_IP="192.168.43.xxx" # ← 电脑在热点中的固定 IP
|
||||||
|
SERVER_PORT=9000
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
LOG_DIR="$HOME/xMonitor/logs"
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
LOG_FILE="$LOG_DIR/monitor_$(date +%Y%m%d_%H%M%S).log"
|
||||||
|
|
||||||
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||||
|
echo "===== $(date) 启动 monitor_sender ====="
|
||||||
|
|
||||||
|
# --- 1) 连接手机热点 ---
|
||||||
|
echo "[NET] 正在连接热点: $HOTSPOT_SSID"
|
||||||
|
MAX_WIFI_RETRY=30
|
||||||
|
for i in $(seq 1 $MAX_WIFI_RETRY); do
|
||||||
|
# 尝试连接(如果已保存过连接配置则直接 up,否则新建)
|
||||||
|
nmcli dev wifi connect "$HOTSPOT_SSID" password "$HOTSPOT_PASS" 2>/dev/null \
|
||||||
|
|| nmcli con up "$HOTSPOT_SSID" 2>/dev/null
|
||||||
|
|
||||||
|
# 检查是否拿到 IP
|
||||||
|
if nmcli -t -f GENERAL.STATE dev show wlan0 2>/dev/null | grep -q "100"; then
|
||||||
|
echo "[NET] WiFi 已连接 ($i/$MAX_WIFI_RETRY)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "[NET] WiFi 未就绪,重试 ($i/$MAX_WIFI_RETRY)..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 2) 等待能 ping 通服务器 ---
|
||||||
|
echo "[NET] 等待服务器 $SERVER_IP 可达..."
|
||||||
|
MAX_PING_RETRY=20
|
||||||
|
for i in $(seq 1 $MAX_PING_RETRY); do
|
||||||
|
if ping -c 1 -W 2 "$SERVER_IP" >/dev/null 2>&1; then
|
||||||
|
echo "[NET] 服务器可达 ✓"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "[NET] ping 失败,重试 ($i/$MAX_PING_RETRY)..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 3) Source 环境并启动 ---
|
||||||
|
echo "[ENV] source ROS2 workspace..."
|
||||||
|
source /home/ubuntu/ros2ws/install/setup.bash
|
||||||
|
|
||||||
|
echo "[ENV] source Python venv..."
|
||||||
|
source "$HOME/xMonitor/.venv/bin/activate"
|
||||||
|
|
||||||
|
echo "[RUN] 启动 monitor_sender.py --ip $SERVER_IP --port $SERVER_PORT"
|
||||||
|
cd "$HOME/xMonitor"
|
||||||
|
python3 monitor_sender.py --ip "$SERVER_IP" --port "$SERVER_PORT"
|
||||||
|
|
||||||
|
EXIT_CODE=$?
|
||||||
|
echo "===== $(date) 进程退出, code=$EXIT_CODE ====="
|
||||||
|
exit $EXIT_CODE
|
||||||
|
|
||||||
|
文件 2:systemd 服务 /etc/systemd/system/xmonitor.service
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=xMonitor Robot Status Sender
|
||||||
|
After=network-manager.service
|
||||||
|
Wants=network-manager.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=ubuntu
|
||||||
|
WorkingDirectory=/home/ubuntu/xMonitor
|
||||||
|
ExecStart=/bin/bash /home/ubuntu/xMonitor/start_monitor.sh
|
||||||
|
|
||||||
|
# --- 崩溃自动重启 ---
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# --- 崩溃日志 ---
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=xmonitor
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
部署步骤(在机器人设备上执行)
|
||||||
|
|
||||||
|
# 1. 将 start_monitor.sh 放到 ~/xMonitor/ 并加执行权限
|
||||||
|
chmod +x ~/xMonitor/start_monitor.sh
|
||||||
|
|
||||||
|
# 2. 编辑 start_monitor.sh 顶部配置区,填入热点名称、密码、电脑IP
|
||||||
|
|
||||||
|
# 3. 复制 service 文件并启用
|
||||||
|
sudo cp xmonitor.service /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable xmonitor.service
|
||||||
|
|
||||||
|
# 4. 立即测试启动(可选)
|
||||||
|
sudo systemctl start xmonitor.service
|
||||||
|
|
||||||
|
# 5. 查看状态 / 日志
|
||||||
|
systemctl status xmonitor.service
|
||||||
|
journalctl -u xmonitor -f # 实时查看日志
|
||||||
|
ls ~/xMonitor/logs/ # 查看历史日志文件
|
||||||
|
|
||||||
|
关键设计说明
|
||||||
|
|
||||||
|
┌────────────┬───────────────────────────────────────────────────────────────┐
|
||||||
|
│ 需求 │ 实现方式 │
|
||||||
|
├────────────┼───────────────────────────────────────────────────────────────┤
|
||||||
|
│ 连热点 │ nmcli dev wifi connect 循环重试,最多 30 次 × 3 秒 │
|
||||||
|
├────────────┼───────────────────────────────────────────────────────────────┤
|
||||||
|
│ 等待网络 │ ping 服务器 IP 直到可达 │
|
||||||
|
├────────────┼───────────────────────────────────────────────────────────────┤
|
||||||
|
│ source │ 脚本内 source .venv/bin/activate │
|
||||||
|
│ 虚拟环境 │ │
|
||||||
|
├────────────┼───────────────────────────────────────────────────────────────┤
|
||||||
|
│ source │ 脚本内 source /home/ubuntu/ros2ws/install/setup.bash(来自 │
|
||||||
|
│ ROS2 │ README) │
|
||||||
|
├────────────┼───────────────────────────────────────────────────────────────┤
|
||||||
|
│ 崩溃重启 │ systemd Restart=on-failure + RestartSec=10(10 秒后重启) │
|
||||||
|
├────────────┼───────────────────────────────────────────────────────────────┤
|
||||||
|
│ 崩溃日志 │ 双保险:① journalctl -u xmonitor 系统日志 ② ~/xMonitor/logs/ │
|
||||||
|
│ │ 下按时间戳命名的文本日志 │
|
||||||
|
├────────────┼───────────────────────────────────────────────────────────────┤
|
||||||
|
│ 开机自启 │ systemctl enable xmonitor.service │
|
||||||
|
└────────────┴───────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
待用户提供
|
||||||
|
|
||||||
|
- 手机热点 SSID
|
||||||
|
- 手机热点密码
|
||||||
|
- 电脑在热点中的固定 IP(192.168.43.???)
|
||||||
|
|
||||||
|
需要创建/修改的文件
|
||||||
|
|
||||||
|
1. 新建 ~/xMonitor/start_monitor.sh — 启动脚本
|
||||||
|
2. 新建 ~/xMonitor/xmonitor.service — systemd 服务文件(用户手动 sudo cp 到
|
||||||
|
/etc/systemd/system/)
|
||||||
|
|
||||||
|
验证方法
|
||||||
|
|
||||||
|
1. 手动运行 bash ~/xMonitor/start_monitor.sh 确认 WiFi 连接和程序启动正常
|
||||||
|
2. sudo systemctl start xmonitor.service 启动服务
|
||||||
|
3. systemctl status xmonitor.service 确认 active (running)
|
||||||
|
4. journalctl -u xmonitor -f 实时查看日志
|
||||||
|
5. 杀掉 python 进程 kill $(pgrep -f monitor_sender) 验证自动重启
|
||||||
|
6. 重启设备验证开机自启
|
||||||
71
README.md
71
README.md
@@ -198,4 +198,75 @@ python3 monitor_sender.py --ip 10.0.0.5 --port 9000
|
|||||||
| `main_monitor.py` | FastAPI server — hosts the web GUI and relays data to browsers |
|
| `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 |
|
| `monitor_sender.py` | ROS 2 node — collects robot data and streams it to the server |
|
||||||
| `README.md` | This file |
|
| `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
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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`
|
||||||
|
|
||||||
|
|||||||
273
WINDOWS_RECEIVER_SETUP.md
Normal file
273
WINDOWS_RECEIVER_SETUP.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# Windows 接收端环境配置
|
||||||
|
|
||||||
|
这份文档只针对接收方,也就是你的 Windows 电脑。
|
||||||
|
|
||||||
|
本文假设:
|
||||||
|
|
||||||
|
- `xMonitor` 仓库就在 Windows 上
|
||||||
|
- 你的电脑在手机热点网络里的固定 IP 是 `10.0.0.5`
|
||||||
|
- 发送端会连到 `10.0.0.5:9000`
|
||||||
|
- 你希望在 Windows 上运行 `main_monitor.py` 来接收机器人状态并在浏览器里显示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 安装 Python
|
||||||
|
|
||||||
|
建议使用 Python 3.10 或更新版本。
|
||||||
|
|
||||||
|
在 PowerShell 里确认 Python 可用:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
py -V
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有安装 Python:
|
||||||
|
|
||||||
|
1. 打开 https://www.python.org/downloads/windows/
|
||||||
|
2. 安装最新版 Python 3
|
||||||
|
3. 安装时勾选 `Add Python to PATH`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 进入项目目录
|
||||||
|
|
||||||
|
在 PowerShell 中进入仓库目录:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd C:\Users\64187\Desktop\Workspace\xMonitor
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 创建并启用虚拟环境
|
||||||
|
|
||||||
|
创建虚拟环境:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
py -3 -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
启用虚拟环境:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 PowerShell 阻止脚本执行,可先临时放开当前会话:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -Scope Process Bypass
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
启用后命令行前面通常会出现 `(.venv)`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 安装接收端依赖
|
||||||
|
|
||||||
|
`main_monitor.py` 接收端只需要这两个包:
|
||||||
|
|
||||||
|
- `fastapi`
|
||||||
|
- `uvicorn`
|
||||||
|
|
||||||
|
安装命令:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install fastapi uvicorn
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 启动接收端
|
||||||
|
|
||||||
|
因为你的发送端现在要往 `10.0.0.5:9000` 发,所以接收端必须监听 `9000` 端口。
|
||||||
|
|
||||||
|
在 PowerShell 中运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uvicorn main_monitor:app --host 0.0.0.0 --port 9000
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `--host 0.0.0.0`:允许热点网络里的设备访问这台 Windows 电脑
|
||||||
|
- `--port 9000`:和发送端启动参数保持一致
|
||||||
|
|
||||||
|
如果你以后改了发送端端口,这里的端口也必须一起改。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 放行 Windows 防火墙
|
||||||
|
|
||||||
|
第一次启动时,Windows 可能会弹出防火墙提示。
|
||||||
|
|
||||||
|
请选择:
|
||||||
|
|
||||||
|
- 允许访问
|
||||||
|
- 至少勾选当前使用的网络类型
|
||||||
|
|
||||||
|
如果没有弹窗,可以手动放行 TCP 9000:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
New-NetFirewallRule `
|
||||||
|
-DisplayName "xMonitor 9000" `
|
||||||
|
-Direction Inbound `
|
||||||
|
-Protocol TCP `
|
||||||
|
-LocalPort 9000 `
|
||||||
|
-Action Allow
|
||||||
|
```
|
||||||
|
|
||||||
|
查看规则是否创建成功:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-NetFirewallRule -DisplayName "xMonitor 9000"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 检查本机热点 IP
|
||||||
|
|
||||||
|
确认 Windows 当前热点网卡上的 IP 确实是你要给发送端使用的固定地址。
|
||||||
|
|
||||||
|
查看 IP:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ipconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
确认对应热点网络适配器上有:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IPv4 Address . . . . . . . . . . : 10.0.0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
如果不是这个地址,要么把 Windows 侧固定 IP 配好,要么把发送端里的 `--ip` 改成实际地址。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 打开监控页面
|
||||||
|
|
||||||
|
接收端启动成功后,在本机浏览器打开:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:9000
|
||||||
|
```
|
||||||
|
|
||||||
|
或者直接打开:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://10.0.0.5:9000
|
||||||
|
```
|
||||||
|
|
||||||
|
如果发送端已经开始推数据,页面会显示机器人状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 验证接收端是否正常工作
|
||||||
|
|
||||||
|
### 看端口是否监听
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
netstat -ano | findstr :9000
|
||||||
|
```
|
||||||
|
|
||||||
|
能看到 `LISTENING` 说明服务已经起来了。
|
||||||
|
|
||||||
|
### 看浏览器页面是否能打开
|
||||||
|
|
||||||
|
打开:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:9000
|
||||||
|
```
|
||||||
|
|
||||||
|
若页面能打开,说明 HTTP 服务正常。
|
||||||
|
|
||||||
|
### 看是否收到机器人数据
|
||||||
|
|
||||||
|
接收端会把机器人上报的数据写到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
logs\robot_packets.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
可以在项目目录下查看:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-Content .\logs\robot_packets.jsonl -Tail 5
|
||||||
|
```
|
||||||
|
|
||||||
|
如果这里不断有新行,说明 WebSocket 接收正常。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 常见问题
|
||||||
|
|
||||||
|
### 1) 浏览器能打开页面,但没有机器人数据
|
||||||
|
|
||||||
|
通常检查这几项:
|
||||||
|
|
||||||
|
- 发送端是否真的发到了 `10.0.0.5:9000`
|
||||||
|
- Windows 防火墙是否放行了 `9000`
|
||||||
|
- 电脑和设备是否确实连在同一个手机热点下
|
||||||
|
- Windows 当前热点 IP 是否还是 `10.0.0.5`
|
||||||
|
|
||||||
|
### 2) PowerShell 里 `Activate.ps1` 不能执行
|
||||||
|
|
||||||
|
先执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -Scope Process Bypass
|
||||||
|
```
|
||||||
|
|
||||||
|
然后再执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) `uvicorn` 命令找不到
|
||||||
|
|
||||||
|
通常是虚拟环境没激活,或者依赖没装成功。
|
||||||
|
|
||||||
|
重新执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
pip install fastapi uvicorn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4) 9000 端口被占用
|
||||||
|
|
||||||
|
查看占用:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
netstat -ano | findstr :9000
|
||||||
|
```
|
||||||
|
|
||||||
|
如果必须换端口,比如改成 `8000`,那就要同时改两边:
|
||||||
|
|
||||||
|
- Windows 接收端启动端口
|
||||||
|
- 发送端 `monitor_sender.py --port ...`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 最小启动流程
|
||||||
|
|
||||||
|
以后你在 Windows 上最少只需要这几步:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd C:\Users\64187\Desktop\Workspace\xMonitor
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
uvicorn main_monitor:app --host 0.0.0.0 --port 9000
|
||||||
|
```
|
||||||
|
|
||||||
|
然后浏览器打开:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:9000
|
||||||
|
```
|
||||||
|
|
||||||
|
如果需要,我下一步可以再给你补一份“Windows 接收端开机自启”文档,直接做成任务计划程序版本。
|
||||||
25
deploy/xmonitor-sender.service
Normal file
25
deploy/xmonitor-sender.service
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Install this file to /etc/systemd/system/xmonitor-sender.service on the target Ubuntu device.
|
||||||
|
# If you do not use /home/ubuntu/xMonitor, update User, WorkingDirectory, ExecStart, and APP_DIR together.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=xMonitor Robot Status Sender
|
||||||
|
After=NetworkManager.service
|
||||||
|
Wants=NetworkManager.service
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=ubuntu
|
||||||
|
WorkingDirectory=/home/ubuntu/xMonitor
|
||||||
|
EnvironmentFile=/etc/xmonitor/xmonitor.env
|
||||||
|
Environment=PYTHONUNBUFFERED=1
|
||||||
|
ExecStart=/home/ubuntu/xMonitor/scripts/start_monitor_sender.sh
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
TimeoutStartSec=0
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=xmonitor-sender
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
19
deploy/xmonitor.env.example
Normal file
19
deploy/xmonitor.env.example
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Copy this file to /etc/xmonitor/xmonitor.env on the target Ubuntu device.
|
||||||
|
# Then edit the values and lock down the permissions:
|
||||||
|
# sudo install -d -m 700 /etc/xmonitor
|
||||||
|
# sudo cp deploy/xmonitor.env.example /etc/xmonitor/xmonitor.env
|
||||||
|
# sudo chmod 600 /etc/xmonitor/xmonitor.env
|
||||||
|
|
||||||
|
HOTSPOT_SSID=YOUR_PHONE_HOTSPOT
|
||||||
|
HOTSPOT_PASSWORD=YOUR_HOTSPOT_PASSWORD
|
||||||
|
SERVER_IP=10.0.0.5
|
||||||
|
SERVER_PORT=9000
|
||||||
|
ROS_SETUP=/home/ubuntu/ros2ws/install/setup.bash
|
||||||
|
APP_DIR=/home/ubuntu/xMonitor
|
||||||
|
RUN_USER=ubuntu
|
||||||
|
|
||||||
|
# Optional overrides.
|
||||||
|
PYTHON_BIN=python3
|
||||||
|
WIFI_RETRY_INTERVAL=5
|
||||||
|
PING_RETRY_INTERVAL=5
|
||||||
|
LOG_RETENTION_COUNT=20
|
||||||
208
scripts/start_monitor_sender.sh
Normal file
208
scripts/start_monitor_sender.sh
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
timestamp() {
|
||||||
|
date '+%Y-%m-%d %H:%M:%S'
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[%s] %s\n' "$(timestamp)" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
log "ERROR: $*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
report_err() {
|
||||||
|
local exit_code=$?
|
||||||
|
local line_no=${1:-unknown}
|
||||||
|
local cmd=${2:-unknown}
|
||||||
|
log "ERROR: command failed at line ${line_no}: ${cmd} (exit=${exit_code})"
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'report_err "${LINENO}" "${BASH_COMMAND}"' ERR
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
DEFAULT_APP_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
RUN_USER="${RUN_USER:-$(id -un)}"
|
||||||
|
APP_DIR="${APP_DIR:-${DEFAULT_APP_DIR}}"
|
||||||
|
LOG_DIR="${LOG_DIR:-${APP_DIR}/logs}"
|
||||||
|
HOTSPOT_SSID="${HOTSPOT_SSID:-}"
|
||||||
|
HOTSPOT_PASSWORD="${HOTSPOT_PASSWORD:-}"
|
||||||
|
SERVER_IP="${SERVER_IP:-10.0.0.5}"
|
||||||
|
SERVER_PORT="${SERVER_PORT:-9000}"
|
||||||
|
ROS_SETUP="${ROS_SETUP:-/home/${RUN_USER}/ros2ws/install/setup.bash}"
|
||||||
|
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||||
|
WIFI_RETRY_INTERVAL="${WIFI_RETRY_INTERVAL:-5}"
|
||||||
|
PING_RETRY_INTERVAL="${PING_RETRY_INTERVAL:-5}"
|
||||||
|
VENV_ACTIVATE="${APP_DIR}/.venv/bin/activate"
|
||||||
|
LOG_RETENTION_COUNT="${LOG_RETENTION_COUNT:-20}"
|
||||||
|
|
||||||
|
mkdir -p "${LOG_DIR}"
|
||||||
|
LOG_FILE="${LOG_DIR}/monitor_sender_$(date '+%Y%m%d_%H%M%S').log"
|
||||||
|
touch "${LOG_FILE}"
|
||||||
|
exec > >(tee -a "${LOG_FILE}") 2>&1
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local command_name=$1
|
||||||
|
command -v "${command_name}" >/dev/null 2>&1 || fail "missing required command: ${command_name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
prune_old_logs() {
|
||||||
|
local prune_from
|
||||||
|
local old_logs=()
|
||||||
|
|
||||||
|
[[ "${LOG_RETENTION_COUNT}" =~ ^[0-9]+$ ]] || fail "LOG_RETENTION_COUNT must be a positive integer"
|
||||||
|
(( LOG_RETENTION_COUNT >= 1 )) || fail "LOG_RETENTION_COUNT must be at least 1"
|
||||||
|
|
||||||
|
prune_from=$((LOG_RETENTION_COUNT + 1))
|
||||||
|
mapfile -t old_logs < <(ls -1dt "${LOG_DIR}"/monitor_sender_*.log 2>/dev/null | tail -n +"${prune_from}")
|
||||||
|
|
||||||
|
if ((${#old_logs[@]} > 0)); then
|
||||||
|
rm -f -- "${old_logs[@]}"
|
||||||
|
log "Pruned ${#old_logs[@]} old log file(s); keeping latest ${LOG_RETENTION_COUNT}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
source_with_relaxed_mode() {
|
||||||
|
local target_file=$1
|
||||||
|
local shell_flags=$-
|
||||||
|
local err_trap
|
||||||
|
local source_status=0
|
||||||
|
|
||||||
|
err_trap="$(trap -p ERR || true)"
|
||||||
|
trap - ERR
|
||||||
|
set +eu
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090,SC1091
|
||||||
|
source "${target_file}"
|
||||||
|
source_status=$?
|
||||||
|
|
||||||
|
[[ "${shell_flags}" == *e* ]] && set -e || set +e
|
||||||
|
[[ "${shell_flags}" == *u* ]] && set -u || set +u
|
||||||
|
|
||||||
|
if [[ -n "${err_trap}" ]]; then
|
||||||
|
eval "${err_trap}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "${source_status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_wifi_device() {
|
||||||
|
nmcli -t -f DEVICE,TYPE device status | awk -F: '$2 == "wifi" { print $1; exit }'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_active_connection_name() {
|
||||||
|
local wifi_device=$1
|
||||||
|
nmcli -g GENERAL.CONNECTION device show "${wifi_device}" 2>/dev/null | head -n1 | tr -d '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_active_ssid() {
|
||||||
|
local wifi_device=$1
|
||||||
|
local active_connection
|
||||||
|
|
||||||
|
active_connection="$(get_active_connection_name "${wifi_device}" || true)"
|
||||||
|
if [[ -z "${active_connection}" || "${active_connection}" == "--" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
nmcli -g 802-11-wireless.ssid connection show "${active_connection}" 2>/dev/null | head -n1 | tr -d '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
is_hotspot_connected() {
|
||||||
|
local wifi_device=$1
|
||||||
|
local active_ssid
|
||||||
|
|
||||||
|
active_ssid="$(get_active_ssid "${wifi_device}" || true)"
|
||||||
|
[[ -n "${active_ssid}" && "${active_ssid}" == "${HOTSPOT_SSID}" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_hotspot() {
|
||||||
|
local wifi_device=$1
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if is_hotspot_connected "${wifi_device}"; then
|
||||||
|
log "Wi-Fi already connected to hotspot '${HOTSPOT_SSID}'"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Trying hotspot '${HOTSPOT_SSID}' on Wi-Fi device '${wifi_device}'"
|
||||||
|
nmcli device wifi rescan ifname "${wifi_device}" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if nmcli connection up "${HOTSPOT_SSID}" ifname "${wifi_device}" >/dev/null 2>&1; then
|
||||||
|
sleep 2
|
||||||
|
if is_hotspot_connected "${wifi_device}"; then
|
||||||
|
log "Connected to hotspot '${HOTSPOT_SSID}' via saved NetworkManager profile"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${HOTSPOT_PASSWORD}" ]]; then
|
||||||
|
if nmcli device wifi connect "${HOTSPOT_SSID}" password "${HOTSPOT_PASSWORD}" ifname "${wifi_device}" >/dev/null 2>&1; then
|
||||||
|
sleep 2
|
||||||
|
if is_hotspot_connected "${wifi_device}"; then
|
||||||
|
log "Connected to hotspot '${HOTSPOT_SSID}' via direct nmcli connect"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "HOTSPOT_PASSWORD is empty; skipping direct nmcli connect and relying on saved profile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Hotspot '${HOTSPOT_SSID}' is not ready yet; retrying in ${WIFI_RETRY_INTERVAL}s"
|
||||||
|
sleep "${WIFI_RETRY_INTERVAL}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_server() {
|
||||||
|
while true; do
|
||||||
|
if ping -c 1 -W 2 "${SERVER_IP}" >/dev/null 2>&1; then
|
||||||
|
log "Server '${SERVER_IP}' is reachable"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Server '${SERVER_IP}' is not reachable yet; retrying in ${PING_RETRY_INTERVAL}s"
|
||||||
|
sleep "${PING_RETRY_INTERVAL}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
log "Starting xMonitor sender bootstrap"
|
||||||
|
log "Using APP_DIR='${APP_DIR}'"
|
||||||
|
log "Log file: ${LOG_FILE}"
|
||||||
|
prune_old_logs
|
||||||
|
|
||||||
|
[[ -n "${HOTSPOT_SSID}" ]] || fail "HOTSPOT_SSID must be set in /etc/xmonitor/xmonitor.env"
|
||||||
|
[[ -d "${APP_DIR}" ]] || fail "APP_DIR does not exist: ${APP_DIR}"
|
||||||
|
[[ -f "${APP_DIR}/monitor_sender.py" ]] || fail "monitor_sender.py not found in ${APP_DIR}"
|
||||||
|
[[ -f "${ROS_SETUP}" ]] || fail "ROS setup file not found: ${ROS_SETUP}"
|
||||||
|
[[ -f "${VENV_ACTIVATE}" ]] || fail "Python virtualenv activate script not found: ${VENV_ACTIVATE}"
|
||||||
|
|
||||||
|
require_command nmcli
|
||||||
|
require_command ping
|
||||||
|
require_command tee
|
||||||
|
require_command "${PYTHON_BIN}"
|
||||||
|
|
||||||
|
if [[ "$(id -un)" != "${RUN_USER}" ]]; then
|
||||||
|
log "Warning: script is running as '$(id -un)' but RUN_USER is '${RUN_USER}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
WIFI_DEVICE="$(get_wifi_device)"
|
||||||
|
[[ -n "${WIFI_DEVICE}" ]] || fail "No Wi-Fi device was found by NetworkManager"
|
||||||
|
|
||||||
|
log "Detected Wi-Fi device '${WIFI_DEVICE}'"
|
||||||
|
connect_hotspot "${WIFI_DEVICE}"
|
||||||
|
wait_for_server
|
||||||
|
|
||||||
|
log "Sourcing ROS environment '${ROS_SETUP}'"
|
||||||
|
source_with_relaxed_mode "${ROS_SETUP}"
|
||||||
|
|
||||||
|
log "Activating Python virtualenv '${VENV_ACTIVATE}'"
|
||||||
|
source_with_relaxed_mode "${VENV_ACTIVATE}"
|
||||||
|
|
||||||
|
cd "${APP_DIR}"
|
||||||
|
log "Launching monitor_sender.py --ip ${SERVER_IP} --port ${SERVER_PORT}"
|
||||||
|
exec "${PYTHON_BIN}" monitor_sender.py --ip "${SERVER_IP}" --port "${SERVER_PORT}"
|
||||||
Reference in New Issue
Block a user