Refactor infrastructure scripts and add mock hardware support
Changes: - Refactor project scripts for better dev/prod workflow separation - Add mock_hardware.py for local development without real hardware - Add Makefile for common commands - Add .env.example for environment variable reference - Split scripts into dev-backend.sh, dev-frontend.sh, prod-backend.sh - Add stop.sh for clean shutdown Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# 环境变量配置示例
|
||||
# 复制此文件为 .env 并修改需要的值
|
||||
|
||||
# ========== 开发模式 ==========
|
||||
# 设置为 1 启用 Mock 硬件模式(本地开发,无需真实硬件)
|
||||
MOCK_HARDWARE=0
|
||||
|
||||
# ========== 后端配置 ==========
|
||||
FLASK_PORT=5000
|
||||
|
||||
# ========== 前端配置 ==========
|
||||
# 前端开发服务器会代理 API 请求到后端
|
||||
BACKEND_URL=http://127.0.0.1:5000
|
||||
|
||||
# ========== 硬件配置(生产环境) ==========
|
||||
AGV_HOST=192.168.60.80
|
||||
ARM_HOST=192.168.60.120
|
||||
|
||||
# ========== 外部 API ==========
|
||||
# TEST_MODE: true=测试环境, false=正式环境
|
||||
TEST_MODE=false
|
||||
@@ -0,0 +1,57 @@
|
||||
.PHONY: help dev dev-backend dev-frontend stop prod install
|
||||
|
||||
help: ## 显示帮助信息
|
||||
@echo "=========================================="
|
||||
@echo " Smart Inspection DevOps"
|
||||
@echo "=========================================="
|
||||
@echo ""
|
||||
@echo "本地开发命令:"
|
||||
@echo " make dev 显示开发模式说明"
|
||||
@echo " make dev-backend 启动后端 (Mock 硬件模式)"
|
||||
@echo " make dev-frontend 启动前端 (Next.js)"
|
||||
@echo " make stop 停止所有开发服务"
|
||||
@echo ""
|
||||
@echo "生产部署命令:"
|
||||
@echo " make prod 启动生产环境 (完整系统)"
|
||||
@echo ""
|
||||
@echo "安装命令:"
|
||||
@echo " make install 安装依赖"
|
||||
@echo ""
|
||||
|
||||
dev: ## 显示开发模式说明
|
||||
@echo "=========================================="
|
||||
@echo " 本地开发模式"
|
||||
@echo "=========================================="
|
||||
@echo ""
|
||||
@echo "需要两个终端:"
|
||||
@echo " 终端 1: make dev-backend"
|
||||
@echo " 终端 2: make dev-frontend"
|
||||
@echo ""
|
||||
@echo "或使用 tmux:"
|
||||
@echo " tmux new-session 'make dev-backend' \\; split-window 'make dev-frontend'"
|
||||
@echo ""
|
||||
|
||||
dev-backend: ## 启动后端开发服务器 (Mock 硬件模式)
|
||||
@./scripts/dev-backend.sh
|
||||
|
||||
dev-frontend: ## 启动前端开发服务器
|
||||
@./scripts/dev-frontend.sh
|
||||
|
||||
stop: ## 停止所有开发服务
|
||||
@./scripts/stop.sh
|
||||
|
||||
prod: ## 启动生产环境 (完整系统)
|
||||
@echo "=========================================="
|
||||
@echo " 生产环境启动"
|
||||
@echo "=========================================="
|
||||
@echo ""
|
||||
@echo "请在 AGV 上运行此命令"
|
||||
@echo ""
|
||||
@./scripts/prod-backend.sh
|
||||
|
||||
install: ## 安装依赖
|
||||
@echo "安装 Python 依赖..."
|
||||
@cd agv_app && uv sync
|
||||
@echo "安装前端依赖..."
|
||||
@cd public-frontend && npm install
|
||||
@echo "完成"
|
||||
+32
-6
@@ -12,13 +12,32 @@ import requests
|
||||
from flask import Flask, render_template, jsonify, request, Response, send_from_directory
|
||||
from flask_cors import CORS
|
||||
|
||||
from config import SERVER_CONFIG, ARM_CONFIG, AGV_CONFIG, UPLOAD_CONFIG, MAP_CONFIG, ARM_CAMERA_CONFIG, CAMERA_CONFIG, DATA_DIR, State, ZHIJIAN_BASE_URL, ZHIJIAN_AUTH_TOKEN, set_api_mode
|
||||
from config import (
|
||||
SERVER_CONFIG, ARM_CONFIG, AGV_CONFIG, UPLOAD_CONFIG, MAP_CONFIG,
|
||||
ARM_CAMERA_CONFIG, CAMERA_CONFIG, DATA_DIR, State, ZHIJIAN_BASE_URL,
|
||||
ZHIJIAN_AUTH_TOKEN, set_api_mode, MOCK_HARDWARE
|
||||
)
|
||||
|
||||
# 根据 MOCK_HARDWARE 配置选择导入真实或 Mock 实现
|
||||
if MOCK_HARDWARE:
|
||||
print("[启动] ===========================================")
|
||||
print("[启动] Mock 硬件模式 - 本地开发环境")
|
||||
print("[启动] ===========================================")
|
||||
from utils.mock_hardware import (
|
||||
MockArmClient as ArmClient,
|
||||
MockAGVController as AGVController,
|
||||
MockQRScanner as QRScanner,
|
||||
MockNav2Navigator as Nav2Navigator,
|
||||
MockNav2Status as Nav2Status
|
||||
)
|
||||
else:
|
||||
from utils.arm_client import ArmClient
|
||||
from utils.agv_controller_ros2 import AGVController
|
||||
from utils.qr_scanner import QRScanner
|
||||
from utils.nav2_navigator import Nav2Navigator, Nav2Status
|
||||
|
||||
from utils.image_uploader import ImageUploader
|
||||
from utils.mission_executor import MissionExecutorV3
|
||||
from utils.nav2_navigator import Nav2Navigator, Nav2Status
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
@@ -133,9 +152,17 @@ try:
|
||||
import threading
|
||||
def _auto_connect_all():
|
||||
time.sleep(2) # 等待 Flask 完全就绪
|
||||
# 连接 AGV
|
||||
if MOCK_HARDWARE:
|
||||
print("[启动] Mock 模式跳过硬件自动连接")
|
||||
# Mock 模式下也创建实例供 API 使用
|
||||
gs.agv_controller = AGVController()
|
||||
gs.arm_client = ArmClient(ARM_CONFIG["host"], ARM_CONFIG["port"])
|
||||
gs.qr_scanner = QRScanner(CAMERA_CONFIG["device_index"])
|
||||
gs.navigator = Nav2Navigator()
|
||||
return
|
||||
|
||||
# 连接 AGV(真实硬件)
|
||||
try:
|
||||
from utils.agv_controller_ros2 import AGVController
|
||||
gs.agv_controller = AGVController()
|
||||
if gs.agv_controller.connect():
|
||||
print("[启动] AGV 自动连接成功")
|
||||
@@ -143,9 +170,8 @@ try:
|
||||
print("[启动] AGV 自动连接失败,请手动连接")
|
||||
except Exception as e:
|
||||
print(f"[启动] AGV 自动连接异常: {e}")
|
||||
# 连接机械臂
|
||||
# 连接机械臂(真实硬件)
|
||||
try:
|
||||
from utils.arm_client import ArmClient
|
||||
gs.arm_client = ArmClient(ARM_CONFIG["host"], ARM_CONFIG["port"])
|
||||
if gs.arm_client.connect():
|
||||
gs.arm_client.power_on()
|
||||
|
||||
@@ -10,6 +10,11 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
AGV_HOST = "192.168.60.80"
|
||||
ARM_HOST = "192.168.60.120"
|
||||
|
||||
# ========== 开发模式 ==========
|
||||
# 设置为 True 时使用 Mock 硬件实现(无需真实硬件)
|
||||
# 通过环境变量 MOCK_HARDWARE=1 启用
|
||||
MOCK_HARDWARE = os.getenv("MOCK_HARDWARE", "0") == "1"
|
||||
|
||||
# ========== AGV 参数 ==========
|
||||
AGV_CONFIG = {
|
||||
"device": "/dev/agvpro_controller",
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
"""
|
||||
Mock 硬件实现 - 用于本地开发环境(无真实硬件)
|
||||
|
||||
通过环境变量 MOCK_HARDWARE=1 启用,模拟所有硬件组件的行为。
|
||||
"""
|
||||
import time
|
||||
import logging
|
||||
import numpy as np
|
||||
from typing import Tuple, List, Optional
|
||||
from enum import Enum
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info("[Mock] 使用 Mock 硬件实现 - 本地开发模式")
|
||||
|
||||
|
||||
class MockArmClient:
|
||||
"""Mock 机械臂客户端"""
|
||||
|
||||
def __init__(self, host: str = "127.0.0.1", port: int = 5002, timeout: float = 10):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.timeout = timeout
|
||||
self._connected = False
|
||||
# 默认关节角度 [J1, J2, J3, J4, J5, J6]
|
||||
self._angles = [0.0, -90.0, 90.0, 0.0, 90.0, 0.0]
|
||||
# 默认坐标 [x, y, z, rx, ry, rz]
|
||||
self._coords = [200.0, 0.0, 300.0, 0.0, 180.0, 0.0]
|
||||
self._power_on = False
|
||||
self._state_on = False
|
||||
# Mock socket for compatibility with real ArmClient interface
|
||||
self._sock = None
|
||||
|
||||
class _MockSocket:
|
||||
"""Mock socket for API compatibility"""
|
||||
def settimeout(self, timeout):
|
||||
pass
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""建立连接(Mock)"""
|
||||
logger.info(f"[Mock] 连接机械臂 {self.host}:{self.port}")
|
||||
self._connected = True
|
||||
self._sock = self._MockSocket() # 创建 mock socket
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
"""关闭连接"""
|
||||
self._connected = False
|
||||
self._sock = None
|
||||
logger.info("[Mock] 关闭机械臂连接")
|
||||
|
||||
def send_command(self, cmd: str) -> Tuple[bool, str]:
|
||||
"""发送命令(Mock)"""
|
||||
if not self._connected:
|
||||
return False, "未连接"
|
||||
logger.debug(f"[Mock] 发送命令: {cmd}")
|
||||
return True, "ok"
|
||||
|
||||
def reconnect(self) -> bool:
|
||||
"""重新连接"""
|
||||
self.close()
|
||||
time.sleep(0.5)
|
||||
return self.connect()
|
||||
|
||||
# ========== 机械臂命令 ==========
|
||||
|
||||
def get_angles(self) -> Tuple[bool, List[float]]:
|
||||
"""获取所有关节角度"""
|
||||
logger.debug(f"[Mock] 获取关节角度: {self._angles}")
|
||||
return True, self._angles
|
||||
|
||||
def set_angles(self, angles: List[float], speed: int = 500) -> bool:
|
||||
"""设置所有关节角度"""
|
||||
if len(angles) != 6:
|
||||
return False
|
||||
logger.info(f"[Mock] 设置关节角度: {angles}, 速度: {speed}")
|
||||
self._angles = list(angles)
|
||||
return True
|
||||
|
||||
def set_angle(self, joint: str, angle: float, speed: int = 500) -> bool:
|
||||
"""设置单个关节角度"""
|
||||
logger.info(f"[Mock] 设置关节 {joint} 角度: {angle}, 速度: {speed}")
|
||||
joint_map = {"J1": 0, "J2": 1, "J3": 2, "J4": 3, "J5": 4, "J6": 5}
|
||||
idx = joint_map.get(joint)
|
||||
if idx is not None:
|
||||
self._angles[idx] = angle
|
||||
return True
|
||||
|
||||
def jog_angle(self, joint: str, direction: int, speed: int = 500) -> bool:
|
||||
"""连续调节关节角度"""
|
||||
logger.debug(f"[Mock] jog_angle({joint}, {direction}, {speed})")
|
||||
return True
|
||||
|
||||
def get_coords(self) -> Tuple[bool, List[float]]:
|
||||
"""获取当前坐标和姿态"""
|
||||
logger.debug(f"[Mock] 获取坐标: {self._coords}")
|
||||
return True, self._coords
|
||||
|
||||
def set_coords(self, coords: List[float], speed: int = 500) -> bool:
|
||||
"""设置坐标和姿态"""
|
||||
if len(coords) != 6:
|
||||
return False
|
||||
logger.info(f"[Mock] 设置坐标: {coords}, 速度: {speed}")
|
||||
self._coords = list(coords)
|
||||
return True
|
||||
|
||||
def jog_coord(self, axis: str, direction: int, speed: int = 500) -> bool:
|
||||
"""连续调节坐标轴"""
|
||||
logger.debug(f"[Mock] jog_coord({axis}, {direction}, {speed})")
|
||||
return True
|
||||
|
||||
def power_on(self) -> bool:
|
||||
"""上电"""
|
||||
logger.info("[Mock] 机械臂上电")
|
||||
self._power_on = True
|
||||
return True
|
||||
|
||||
def state_on(self) -> bool:
|
||||
"""启用状态"""
|
||||
logger.info("[Mock] 机械臂状态启用")
|
||||
self._state_on = True
|
||||
return True
|
||||
|
||||
def state_off(self) -> bool:
|
||||
"""禁用状态"""
|
||||
logger.info("[Mock] 机械臂状态禁用")
|
||||
self._state_on = False
|
||||
return True
|
||||
|
||||
def state_check(self) -> bool:
|
||||
"""检查机械臂状态"""
|
||||
return self._state_on
|
||||
|
||||
def check_running(self) -> bool:
|
||||
"""检查机械臂是否在运行"""
|
||||
return False # Mock模式下始终不在运行
|
||||
|
||||
def wait_done(self, timeout: float = 30) -> bool:
|
||||
"""等待上一条命令执行完成"""
|
||||
logger.debug(f"[Mock] wait_done({timeout}s) - 立即返回")
|
||||
return True
|
||||
|
||||
def task_stop(self) -> bool:
|
||||
"""停止任务"""
|
||||
logger.info("[Mock] 停止机械臂任务")
|
||||
return True
|
||||
|
||||
def __enter__(self):
|
||||
self.connect()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
||||
class MockAGVController:
|
||||
"""Mock AGV 控制器"""
|
||||
|
||||
def __init__(self, device: str = "/dev/agvpro_controller", baudrate: int = 1000000):
|
||||
self.device = device
|
||||
self.baudrate = baudrate
|
||||
self._connected = False
|
||||
self._position = [0.0, 0.0, 0.0] # [x, y, yaw]
|
||||
self._voltage = 48.0 # 模拟电压
|
||||
self._moving = False
|
||||
|
||||
def _run_ros2_cmd(self, cmd: str, timeout: float = 5.0) -> tuple:
|
||||
"""执行 ros2 命令(Mock)"""
|
||||
logger.debug(f"[Mock] ros2 命令: {cmd}")
|
||||
return 0, "", ""
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""连接 AGV(Mock)"""
|
||||
logger.info(f"[Mock] 连接 AGV 控制器 {self.device}")
|
||||
self._connected = True
|
||||
return True
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
"""检查是否已连接"""
|
||||
return self._connected
|
||||
|
||||
def move_forward(self, speed: float = 1.0, duration: float = None):
|
||||
"""前进"""
|
||||
logger.info(f"[Mock] AGV 前进,速度: {speed}, 时长: {duration}")
|
||||
self._moving = True
|
||||
if duration:
|
||||
time.sleep(min(duration, 0.1)) # Mock 模式下只短暂等待
|
||||
self.stop()
|
||||
|
||||
def move_backward(self, speed: float = 1.0, duration: float = None):
|
||||
"""后退"""
|
||||
logger.info(f"[Mock] AGV 后退,速度: {speed}, 时长: {duration}")
|
||||
if duration:
|
||||
time.sleep(min(duration, 0.1))
|
||||
self.stop()
|
||||
|
||||
def turn_left(self, speed: float = 1.0, duration: float = None):
|
||||
"""左转"""
|
||||
logger.info(f"[Mock] AGV 左转,速度: {speed}, 时长: {duration}")
|
||||
if duration:
|
||||
time.sleep(min(duration, 0.1))
|
||||
self.stop()
|
||||
|
||||
def turn_right(self, speed: float = 1.0, duration: float = None):
|
||||
"""右转"""
|
||||
logger.info(f"[Mock] AGV 右转,速度: {speed}, 时长: {duration}")
|
||||
if duration:
|
||||
time.sleep(min(duration, 0.1))
|
||||
self.stop()
|
||||
|
||||
def move_left_lateral(self, speed: float = 1.0, duration: float = None):
|
||||
"""向左横向移动"""
|
||||
logger.info(f"[Mock] AGV 向左横向移动,速度: {speed}, 时长: {duration}")
|
||||
if duration:
|
||||
time.sleep(min(duration, 0.1))
|
||||
self.stop()
|
||||
|
||||
def move_right_lateral(self, speed: float = 1.0, duration: float = None):
|
||||
"""向右横向移动"""
|
||||
logger.info(f"[Mock] AGV 向右横向移动,速度: {speed}, 时长: {duration}")
|
||||
if duration:
|
||||
time.sleep(min(duration, 0.1))
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
"""停止"""
|
||||
logger.debug("[Mock] AGV 停止")
|
||||
self._moving = False
|
||||
|
||||
def get_position(self) -> Optional[List[float]]:
|
||||
"""获取 AGV 当前位置"""
|
||||
logger.debug(f"[Mock] 获取 AGV 位置: {self._position}")
|
||||
return self._position
|
||||
|
||||
def go_to_point(self, x: float, y: float, rz: float = None, speed: float = 1.0) -> bool:
|
||||
"""移动到目标点"""
|
||||
logger.info(f"[Mock] AGV 移动到目标点: ({x}, {y}), yaw: {rz}")
|
||||
# 模拟移动完成
|
||||
self._position = [x, y, rz if rz is not None else self._position[2]]
|
||||
return True
|
||||
|
||||
def get_battery(self) -> Optional[float]:
|
||||
"""获取电池电压"""
|
||||
logger.debug(f"[Mock] 获取电池电压: {self._voltage}V")
|
||||
return self._voltage
|
||||
|
||||
def disconnect(self):
|
||||
"""断开连接"""
|
||||
self.stop()
|
||||
self._connected = False
|
||||
|
||||
def __enter__(self):
|
||||
self.connect()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.disconnect()
|
||||
|
||||
|
||||
class MockQRScanner:
|
||||
"""Mock 二维码扫描器"""
|
||||
|
||||
def __init__(self, device_index: int = 0, width: int = 640, height: int = 400, prefer_v4l2_ctl: bool = True):
|
||||
self.device_index = device_index
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._opened = False
|
||||
# Mock 二维码内容
|
||||
self._mock_qr_code = "MOCK_QR_SN_12345"
|
||||
|
||||
def open(self) -> bool:
|
||||
"""打开摄像头(Mock)"""
|
||||
logger.info(f"[Mock] 打开摄像头 /dev/video{self.device_index}")
|
||||
self._opened = True
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
"""关闭摄像头"""
|
||||
self._opened = False
|
||||
logger.info("[Mock] 关闭摄像头")
|
||||
|
||||
def read_frame(self, timeout: float = 2.0) -> Optional[np.ndarray]:
|
||||
"""读取一帧(Mock)"""
|
||||
if not self._opened:
|
||||
return None
|
||||
# 返回空白图像
|
||||
return np.zeros((self.height, self.width, 3), dtype=np.uint8)
|
||||
|
||||
def detect_qr(self, frame: np.ndarray) -> Optional[str]:
|
||||
"""从图像帧中检测二维码(Mock)"""
|
||||
# 始终返回模拟二维码
|
||||
logger.debug(f"[Mock] 检测到二维码: {self._mock_qr_code}")
|
||||
return self._mock_qr_code
|
||||
|
||||
def scan_once(self) -> Optional[str]:
|
||||
"""扫描一次(Mock)"""
|
||||
logger.debug("[Mock] 扫描二维码...")
|
||||
return self._mock_qr_code
|
||||
|
||||
def scan_with_retry(self, max_attempts: int = 5, interval: float = 0.5) -> Optional[str]:
|
||||
"""多次扫描(Mock)"""
|
||||
logger.info(f"[Mock] scan_with_retry - 直接返回模拟二维码")
|
||||
return self._mock_qr_code
|
||||
|
||||
def get_preview_frame(self) -> Optional[np.ndarray]:
|
||||
"""获取预览帧(Mock)"""
|
||||
return self.read_frame()
|
||||
|
||||
def set_mock_qr_code(self, code: str):
|
||||
"""设置模拟二维码内容(仅 Mock 模式)"""
|
||||
self._mock_qr_code = code
|
||||
logger.info(f"[Mock] 设置模拟二维码: {code}")
|
||||
|
||||
def __enter__(self):
|
||||
self.open()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
||||
class MockNav2Status(Enum):
|
||||
"""Mock Nav2 状态"""
|
||||
IDLE = "idle"
|
||||
NAVIGATING = "navigating"
|
||||
SUCCEEDED = "succeeded"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
class MockNav2Navigator:
|
||||
"""Mock Nav2 导航器"""
|
||||
|
||||
def __init__(self):
|
||||
self.status = MockNav2Status.IDLE
|
||||
self._current_pose = [0.0, 0.0, 0.0] # [x, y, yaw]
|
||||
|
||||
def _get_current_pose(self) -> List[float]:
|
||||
"""获取当前位置(Mock)"""
|
||||
return self._current_pose
|
||||
|
||||
def navigate_to_pose(self, x: float, y: float, yaw: float = None,
|
||||
timeout_sec: float = 120.0,
|
||||
blocking: bool = True) -> bool:
|
||||
"""导航到目标坐标(Mock)"""
|
||||
logger.info(f"[Mock] 导航到: ({x:.3f}, {y:.3f}), yaw={yaw:.1f}°")
|
||||
self.status = MockNav2Status.NAVIGATING
|
||||
# 模拟导航成功
|
||||
self._current_pose = [x, y, yaw if yaw is not None else 0.0]
|
||||
self.status = MockNav2Status.SUCCEEDED
|
||||
return True
|
||||
|
||||
def navigate_through_poses(self, poses: List[Tuple[float, float, float]],
|
||||
timeout_per_pose: float = 120.0,
|
||||
blocking: bool = True) -> bool:
|
||||
"""通过多个点位导航(Mock)"""
|
||||
logger.info(f"[Mock] 通过 {len(poses)} 个点位导航")
|
||||
for i, (x, y, yaw) in enumerate(poses):
|
||||
logger.debug(f"[Mock] 点位 {i+1}: ({x}, {y}), yaw={yaw}")
|
||||
if poses:
|
||||
self._current_pose = list(poses[-1])
|
||||
self.status = MockNav2Status.SUCCEEDED
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
"""停止导航(Mock)"""
|
||||
logger.info("[Mock] 停止导航")
|
||||
self.status = MockNav2Status.IDLE
|
||||
|
||||
def get_status(self) -> dict:
|
||||
"""获取导航状态(Mock)"""
|
||||
return {
|
||||
"status": self.status.value,
|
||||
"position": self._current_pose
|
||||
}
|
||||
|
||||
def get_current_position(self) -> List[float]:
|
||||
"""获取当前位置(Mock)"""
|
||||
return self._current_pose
|
||||
|
||||
|
||||
# 导出兼容别名
|
||||
Nav2Status = MockNav2Status
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# dev-backend.sh - 本地后端开发启动(Mock 硬件模式)
|
||||
# 用法: ./scripts/dev-backend.sh
|
||||
# 说明: 启动 Flask 后端,使用 Mock 硬件实现,无需真实硬件
|
||||
# ============================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
AGV_APP_DIR="$PROJECT_DIR/agv_app"
|
||||
|
||||
echo "=========================================="
|
||||
echo " 本地开发模式 - Flask 后端 (Mock 硬件)"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo " Mock 硬件模式已启用:"
|
||||
echo " - AGV 控制器: Mock"
|
||||
echo " - 机械臂: Mock"
|
||||
echo " - 摄像头: Mock"
|
||||
echo " - Nav2 导航: Mock"
|
||||
echo ""
|
||||
echo " 访问: http://127.0.0.1:5000"
|
||||
echo " Ctrl+C 停止"
|
||||
echo ""
|
||||
|
||||
# 设置环境变量启用 Mock 模式
|
||||
export MOCK_HARDWARE=1
|
||||
export FLASK_PORT=5000
|
||||
|
||||
cd "$AGV_APP_DIR"
|
||||
exec uv run --locked python app.py
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# dev-frontend.sh - 前端开发启动
|
||||
# 用法: ./scripts/dev-frontend.sh
|
||||
# 说明: 启动 Next.js 开发服务器,API 代理到后端
|
||||
# ============================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
FRONTEND_DIR="$SCRIPT_DIR/../public-frontend"
|
||||
|
||||
echo "=========================================="
|
||||
echo " 前端开发模式 - Next.js"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo " 后端 URL: ${BACKEND_URL:-http://127.0.0.1:5000}"
|
||||
echo " 访问: http://localhost:3000"
|
||||
echo " Ctrl+C 停止"
|
||||
echo ""
|
||||
|
||||
# 确保后端 URL 设置(默认本地)
|
||||
export BACKEND_URL=${BACKEND_URL:-http://127.0.0.1:5000}
|
||||
export NEXT_PUBLIC_BACKEND_URL=${BACKEND_URL}
|
||||
|
||||
cd "$FRONTEND_DIR"
|
||||
exec npm run dev
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# dev_start.sh - 本地开发环境启动(不启动 ROS2/机械臂硬件)
|
||||
# 用法: ./scripts/dev_start.sh
|
||||
# ============================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
AGV_APP_DIR="$PROJECT_DIR/agv_app"
|
||||
AGV_ROS2_DIR="${AGV_ROS2_DIR:-$HOME/agv_pro_ros2}"
|
||||
ROS_DISTRO="${ROS_DISTRO:-humble}"
|
||||
ROS_SETUP="${ROS_SETUP:-/opt/ros/$ROS_DISTRO/setup.bash}"
|
||||
ROS_WORKSPACE_SETUP="${ROS_WORKSPACE_SETUP:-$AGV_ROS2_DIR/install/setup.bash}"
|
||||
FLASK_PORT="${FLASK_PORT:-5000}"
|
||||
|
||||
echo "=========================================="
|
||||
echo " 本地开发模式 - 仅启动 Flask"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 切换到项目目录
|
||||
source "$ROS_SETUP" 2>/dev/null || true
|
||||
source "$ROS_WORKSPACE_SETUP" 2>/dev/null || true
|
||||
|
||||
cd "$AGV_APP_DIR"
|
||||
|
||||
# 检查是否有运行的 Flask 进程
|
||||
FLASK_PID=$(pgrep -f "python.*app.py" 2>/dev/null || true)
|
||||
if [ -n "$FLASK_PID" ]; then
|
||||
echo "Flask 已在运行 (PID: $FLASK_PID)"
|
||||
read -p "是否重启? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
kill "$FLASK_PID" 2>/dev/null
|
||||
sleep 1
|
||||
else
|
||||
echo "保持现有进程,退出"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 使用前台模式运行(方便看日志和 Ctrl+C 停止)
|
||||
echo "启动 Flask (前台模式,Ctrl+C 停止)..."
|
||||
echo "访问: http://127.0.0.1:$FLASK_PORT"
|
||||
echo ""
|
||||
exec uv run --locked python app.py
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# restart_flask.sh - 语法检查 + 重启 Flask + 验证
|
||||
# 用法: ssh elephant@<AGV_IP> 'bash -s' < scripts/restart_flask.sh
|
||||
# 或在 AGV 上: cd ~/work/smart-inspection && ./scripts/restart_flask.sh
|
||||
# ============================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
AGV_PROJECT_DIR="${AGV_PROJECT_DIR:-$PROJECT_DIR}"
|
||||
AGV_APP_DIR="${AGV_APP_DIR:-$AGV_PROJECT_DIR/agv_app}"
|
||||
AGV_ROS2_DIR="${AGV_ROS2_DIR:-$HOME/agv_pro_ros2}"
|
||||
ROS_DISTRO="${ROS_DISTRO:-humble}"
|
||||
ROS_SETUP="${ROS_SETUP:-/opt/ros/$ROS_DISTRO/setup.bash}"
|
||||
ROS_WORKSPACE_SETUP="${ROS_WORKSPACE_SETUP:-$AGV_ROS2_DIR/install/setup.bash}"
|
||||
LOG_DIR="${LOG_DIR:-/tmp}"
|
||||
FLASK_PORT="${FLASK_PORT:-5000}"
|
||||
FLASK_LOG="$LOG_DIR/agv_flask.log"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
source "$ROS_SETUP" 2>/dev/null || true
|
||||
source "$ROS_WORKSPACE_SETUP" 2>/dev/null || true
|
||||
|
||||
cd "$AGV_APP_DIR"
|
||||
|
||||
echo "=========================================="
|
||||
echo " 重启 Flask 服务"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 1. 语法检查
|
||||
echo "[1/3] Python 语法检查..."
|
||||
uv run --locked python -m py_compile app.py
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ 语法错误,请先修复"
|
||||
exit 1
|
||||
fi
|
||||
echo " ✅ 语法检查通过"
|
||||
|
||||
# 2. 清缓存 + 重启
|
||||
echo "[2/3] 清理缓存并重启..."
|
||||
find "$AGV_APP_DIR" -name '*.pyc' -delete 2>/dev/null
|
||||
find "$AGV_APP_DIR" -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null
|
||||
|
||||
pkill -f "python.*app.py" 2>/dev/null || true
|
||||
pkill -f "uv run .*python app.py" 2>/dev/null || true
|
||||
sleep 1
|
||||
nohup uv run --locked python app.py > "$FLASK_LOG" 2>&1 &
|
||||
FLASK_PID=$!
|
||||
echo " Flask PID: $FLASK_PID"
|
||||
|
||||
# 3. 验证
|
||||
echo "[3/3] 验证服务..."
|
||||
sleep 3
|
||||
if ss -tlnp 2>/dev/null | grep -q ":$FLASK_PORT " || netstat -tlnp 2>/dev/null | grep -q ":$FLASK_PORT "; then
|
||||
echo " ✅ 端口 $FLASK_PORT 正常监听"
|
||||
# 测试机械臂摄像头单帧
|
||||
result=$(curl -s --max-time 5 "http://127.0.0.1:$FLASK_PORT/api/camera/arm_refresh" 2>/dev/null | head -c 4)
|
||||
if [ "$result" = "$(echo -en '\xff\xd8\xff\xe0')" ]; then
|
||||
echo " ✅ arm_refresh 返回 JPEG"
|
||||
else
|
||||
echo " ⚠️ arm_refresh 返回异常(机械臂可能未连接)"
|
||||
fi
|
||||
else
|
||||
echo " ❌ 端口 $FLASK_PORT 未监听,查看日志:"
|
||||
tail -10 "$FLASK_LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " ✅ 重启完成"
|
||||
echo "=========================================="
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# start_flask.sh - 仅启动/重启 Flask 服务(不启动 ROS2)
|
||||
# 适用于: 修改了前端/API 代码后快速重启
|
||||
# ============================================================
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
AGV_PROJECT_DIR="${AGV_PROJECT_DIR:-$PROJECT_DIR}"
|
||||
AGV_APP_DIR="${AGV_APP_DIR:-$AGV_PROJECT_DIR/agv_app}"
|
||||
AGV_ROS2_DIR="${AGV_ROS2_DIR:-$HOME/agv_pro_ros2}"
|
||||
ROS_DISTRO="${ROS_DISTRO:-humble}"
|
||||
ROS_SETUP="${ROS_SETUP:-/opt/ros/$ROS_DISTRO/setup.bash}"
|
||||
ROS_WORKSPACE_SETUP="${ROS_WORKSPACE_SETUP:-$AGV_ROS2_DIR/install/setup.bash}"
|
||||
LOG_DIR="${LOG_DIR:-/tmp}"
|
||||
FLASK_PORT="${FLASK_PORT:-5000}"
|
||||
FLASK_LOG="$LOG_DIR/agv_flask.log"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
pkill -f "python.*app.py" 2>/dev/null || true
|
||||
pkill -f "uv run .*python app.py" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
source "$ROS_SETUP" 2>/dev/null || true
|
||||
source "$ROS_WORKSPACE_SETUP" 2>/dev/null || true
|
||||
|
||||
cd "$AGV_APP_DIR"
|
||||
nohup uv run --locked python app.py > "$FLASK_LOG" 2>&1 &
|
||||
echo "Flask started, PID: $!"
|
||||
sleep 2
|
||||
|
||||
if ss -tlnp 2>/dev/null | grep -q ":$FLASK_PORT " || netstat -tlnp 2>/dev/null | grep -q ":$FLASK_PORT "; then
|
||||
echo "✅ 端口 $FLASK_PORT 正常"
|
||||
else
|
||||
echo "⚠️ 端口 $FLASK_PORT 未监听,检查 $FLASK_LOG"
|
||||
fi
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# stop.sh - 停止开发服务
|
||||
# 用法: ./scripts/stop.sh
|
||||
# 说明: 停止 Flask 和 Next.js 开发服务器
|
||||
# ============================================================
|
||||
set -e
|
||||
|
||||
echo "=========================================="
|
||||
echo " 停止开发服务"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 停止 Flask
|
||||
if pgrep -f "python.*app.py" > /dev/null 2>&1; then
|
||||
echo "停止 Flask..."
|
||||
pkill -f "python.*app.py" || true
|
||||
pkill -f "uv run .*python app.py" || true
|
||||
echo " ✓ Flask 已停止"
|
||||
else
|
||||
echo " - Flask 未运行"
|
||||
fi
|
||||
|
||||
# 停止 Next.js
|
||||
if pgrep -f "next dev" > /dev/null 2>&1; then
|
||||
echo "停止 Next.js..."
|
||||
pkill -f "next dev" || true
|
||||
echo " ✓ Next.js 已停止"
|
||||
else
|
||||
echo " - Next.js 未运行"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "完成"
|
||||
Reference in New Issue
Block a user