Improve camera status and production startup
This commit is contained in:
+8
-16
@@ -207,16 +207,7 @@ def running_page():
|
|||||||
def api_status():
|
def api_status():
|
||||||
"""获取系统整体状态"""
|
"""获取系统整体状态"""
|
||||||
with gs.lock:
|
with gs.lock:
|
||||||
# 实际验证机械臂连接(尝试发送一个简单命令)
|
arm_connected = gs.arm_client.is_connected() if gs.arm_client else False
|
||||||
arm_connected = False
|
|
||||||
if gs.arm_client and gs.arm_client._sock:
|
|
||||||
try:
|
|
||||||
# 设置短超时尝试获取角度,验证连接是否有效
|
|
||||||
gs.arm_client._sock.settimeout(2)
|
|
||||||
ok, _ = gs.arm_client.get_angles()
|
|
||||||
arm_connected = ok
|
|
||||||
except:
|
|
||||||
arm_connected = False
|
|
||||||
|
|
||||||
# 实际验证 AGV 连接
|
# 实际验证 AGV 连接
|
||||||
agv_connected = False
|
agv_connected = False
|
||||||
@@ -239,6 +230,8 @@ def api_status():
|
|||||||
"camera_opened": gs.camera_opened,
|
"camera_opened": gs.camera_opened,
|
||||||
"arm_camera_opened": gs.arm_camera_opened,
|
"arm_camera_opened": gs.arm_camera_opened,
|
||||||
"map_loaded": bool(gs.map_config),
|
"map_loaded": bool(gs.map_config),
|
||||||
|
"map": gs.map_config,
|
||||||
|
"has_agv_camera": gs.camera_opened,
|
||||||
"points_count": len(gs.points_config),
|
"points_count": len(gs.points_config),
|
||||||
"models_count": len(gs.models_config),
|
"models_count": len(gs.models_config),
|
||||||
"mission_rows": gs.mission_config.get("rows", 0),
|
"mission_rows": gs.mission_config.get("rows", 0),
|
||||||
@@ -1111,7 +1104,7 @@ def api_arm_state_off():
|
|||||||
@app.route("/api/camera/preview")
|
@app.route("/api/camera/preview")
|
||||||
def api_camera_preview():
|
def api_camera_preview():
|
||||||
"""MJPEG 视频流"""
|
"""MJPEG 视频流"""
|
||||||
if not gs.qr_scanner or not gs.qr_scanner._cap:
|
if not gs.qr_scanner or not gs.camera_opened:
|
||||||
return "camera not opened", 400
|
return "camera not opened", 400
|
||||||
|
|
||||||
import time as _time
|
import time as _time
|
||||||
@@ -1137,7 +1130,7 @@ def api_camera_preview():
|
|||||||
@app.route("/api/camera/capture")
|
@app.route("/api/camera/capture")
|
||||||
def api_camera_capture():
|
def api_camera_capture():
|
||||||
"""拍摄一张照片"""
|
"""拍摄一张照片"""
|
||||||
if not gs.qr_scanner or not gs.qr_scanner._cap:
|
if not gs.qr_scanner or not gs.camera_opened:
|
||||||
return jsonify({"ok": False, "error": "摄像头未打开"}), 400
|
return jsonify({"ok": False, "error": "摄像头未打开"}), 400
|
||||||
import cv2
|
import cv2
|
||||||
frame = gs.qr_scanner.read_frame()
|
frame = gs.qr_scanner.read_frame()
|
||||||
@@ -2107,7 +2100,7 @@ def api_camera_refresh():
|
|||||||
"""AGV 摄像头单帧 JPEG(polling 模式)"""
|
"""AGV 摄像头单帧 JPEG(polling 模式)"""
|
||||||
if not gs.qr_scanner:
|
if not gs.qr_scanner:
|
||||||
return jsonify({"error": "scanner not initialized"}), 400
|
return jsonify({"error": "scanner not initialized"}), 400
|
||||||
if not gs.qr_scanner._cap or not gs.qr_scanner._cap.isOpened():
|
if not gs.camera_opened:
|
||||||
return jsonify({"error": "camera not opened"}), 400
|
return jsonify({"error": "camera not opened"}), 400
|
||||||
import cv2
|
import cv2
|
||||||
frame = gs.qr_scanner.read_frame()
|
frame = gs.qr_scanner.read_frame()
|
||||||
@@ -2126,8 +2119,8 @@ def api_camera_refresh():
|
|||||||
def api_camera_capabilities():
|
def api_camera_capabilities():
|
||||||
"""返回摄像头能力信息,前端据此决定如何展示"""
|
"""返回摄像头能力信息,前端据此决定如何展示"""
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"has_agv_camera": False, # Orbbec 深度相机不提供可用的彩色画面
|
"has_agv_camera": bool(gs.camera_opened),
|
||||||
"has_arm_camera": True,
|
"has_arm_camera": bool(gs.arm_camera_opened),
|
||||||
})
|
})
|
||||||
# ========== 启动 ==========
|
# ========== 启动 ==========
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -2155,4 +2148,3 @@ if __name__ == "__main__":
|
|||||||
debug=SERVER_CONFIG["debug"],
|
debug=SERVER_CONFIG["debug"],
|
||||||
threaded=True
|
threaded=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ class ArmClient:
|
|||||||
self._sock.close()
|
self._sock.close()
|
||||||
self._sock = None
|
self._sock = None
|
||||||
|
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
"""返回当前 TCP 连接是否仍由客户端持有。"""
|
||||||
|
return self._sock is not None
|
||||||
|
|
||||||
def reconnect(self) -> bool:
|
def reconnect(self) -> bool:
|
||||||
self.close()
|
self.close()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const CameraFrame: React.FC<CameraFrameProps> = ({ camera, active = true,
|
|||||||
{/* 后端的 AGV 接口是单帧 JPEG,机械臂接口是 MJPEG;img 可同时承载两者。 */}
|
{/* 后端的 AGV 接口是单帧 JPEG,机械臂接口是 MJPEG;img 可同时承载两者。 */}
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
key={isPollingJpeg ? reloadKey : camera.id}
|
key={camera.id}
|
||||||
className="cameraFrameImage"
|
className="cameraFrameImage"
|
||||||
src={streamUrl}
|
src={streamUrl}
|
||||||
alt={camera.name}
|
alt={camera.name}
|
||||||
|
|||||||
+23
-7
@@ -46,7 +46,6 @@ info "ok" "清理完成"
|
|||||||
# 2. 启动 ros2 daemon
|
# 2. 启动 ros2 daemon
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
step "2/8" "启动 ros2 daemon"
|
step "2/8" "启动 ros2 daemon"
|
||||||
source "$ROS_SETUP" 2>/dev/null || true
|
|
||||||
rm -rf "$FASTRTPS_SHM_DIR"/fastrtps_* 2>/dev/null || true
|
rm -rf "$FASTRTPS_SHM_DIR"/fastrtps_* 2>/dev/null || true
|
||||||
start_ros2_daemon || true
|
start_ros2_daemon || true
|
||||||
|
|
||||||
@@ -55,12 +54,16 @@ start_ros2_daemon || true
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
step "3/8" "启动 AGV Bringup"
|
step "3/8" "启动 AGV Bringup"
|
||||||
cd "$AGV_ROS2_DIR"
|
cd "$AGV_ROS2_DIR"
|
||||||
source "$ROS_WORKSPACE_SETUP"
|
|
||||||
|
|
||||||
rm -rf "$FASTRTPS_SHM_DIR"/fastrtps_* 2>/dev/null || true
|
rm -rf "$FASTRTPS_SHM_DIR"/fastrtps_* 2>/dev/null || true
|
||||||
|
|
||||||
nohup bash -c "export ROS_DOMAIN_ID=$ROS_DOMAIN_ID && \
|
nohup bash -c '
|
||||||
ros2 launch agv_pro_bringup agv_pro_bringup.launch.py port_name:=$AGV_CONTROLLER_DEVICE" \
|
source "$1" || exit 1
|
||||||
|
source "$2" || exit 1
|
||||||
|
export ROS_DOMAIN_ID="$3"
|
||||||
|
cd "$4" || exit 1
|
||||||
|
ros2 launch agv_pro_bringup agv_pro_bringup.launch.py port_name:="$5"
|
||||||
|
' _ "$ROS_SETUP" "$ROS_WORKSPACE_SETUP" "$ROS_DOMAIN_ID" "$AGV_ROS2_DIR" "$AGV_CONTROLLER_DEVICE" \
|
||||||
> "$BRINGUP_LOG" 2>&1 &
|
> "$BRINGUP_LOG" 2>&1 &
|
||||||
BRINGUP_PID=$!
|
BRINGUP_PID=$!
|
||||||
echo " bringup PID: $BRINGUP_PID"
|
echo " bringup PID: $BRINGUP_PID"
|
||||||
@@ -123,7 +126,7 @@ step "6/8" "启动 Nav2 导航"
|
|||||||
|
|
||||||
nohup bash -c "source \"$ROS_SETUP\" && source \"$ROS_WORKSPACE_SETUP\" && \
|
nohup bash -c "source \"$ROS_SETUP\" && source \"$ROS_WORKSPACE_SETUP\" && \
|
||||||
export ROS_DOMAIN_ID=$ROS_DOMAIN_ID && \
|
export ROS_DOMAIN_ID=$ROS_DOMAIN_ID && \
|
||||||
ros2 launch agv_pro_navigation2 navigation2_active.launch.py autostart:=True" \
|
ros2 launch agv_pro_navigation2 navigation2_active.launch.py autostart:=True use_rviz:=False" \
|
||||||
> "$NAV2_LOG" 2>&1 &
|
> "$NAV2_LOG" 2>&1 &
|
||||||
NAV2_PID=$!
|
NAV2_PID=$!
|
||||||
echo " Nav2 PID: $NAV2_PID"
|
echo " Nav2 PID: $NAV2_PID"
|
||||||
@@ -153,9 +156,22 @@ info "ok" "精度参数已设置"
|
|||||||
step "8/8" "启动 Flask API"
|
step "8/8" "启动 Flask API"
|
||||||
|
|
||||||
cd "$AGV_APP_DIR"
|
cd "$AGV_APP_DIR"
|
||||||
nohup uv run --locked python app.py > "$FLASK_LOG" 2>&1 &
|
UV_EXECUTABLE=$(resolve_uv_bin || true)
|
||||||
|
FLASK_PID=""
|
||||||
|
if [ -z "$UV_EXECUTABLE" ]; then
|
||||||
|
echo " [ERROR] 未找到 uv,可设置 UV_BIN=/path/to/uv"
|
||||||
|
else
|
||||||
|
nohup bash -c '
|
||||||
|
source "$1" || exit 1
|
||||||
|
source "$2" || exit 1
|
||||||
|
export ROS_DOMAIN_ID="$3"
|
||||||
|
cd "$4" || exit 1
|
||||||
|
exec "$5" run --locked python app.py
|
||||||
|
' _ "$ROS_SETUP" "$ROS_WORKSPACE_SETUP" "$ROS_DOMAIN_ID" "$AGV_APP_DIR" "$UV_EXECUTABLE" \
|
||||||
|
> "$FLASK_LOG" 2>&1 &
|
||||||
FLASK_PID=$!
|
FLASK_PID=$!
|
||||||
echo " Flask PID: $FLASK_PID"
|
echo " Flask PID: $FLASK_PID"
|
||||||
|
fi
|
||||||
sleep 4
|
sleep 4
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -223,7 +239,7 @@ echo " 进程状态:"
|
|||||||
for proc_info in "bringup:$BRINGUP_PID" "Nav2:$NAV2_PID" "fixer:$FIXER_PID" "Flask:$FLASK_PID"; do
|
for proc_info in "bringup:$BRINGUP_PID" "Nav2:$NAV2_PID" "fixer:$FIXER_PID" "Flask:$FLASK_PID"; do
|
||||||
name="${proc_info%%:*}"
|
name="${proc_info%%:*}"
|
||||||
pid="${proc_info##*:}"
|
pid="${proc_info##*:}"
|
||||||
if ps aux | grep -w "$pid" | grep -v grep >/dev/null 2>&1; then
|
if [ -n "$pid" ] && ps -p "$pid" >/dev/null 2>&1; then
|
||||||
echo " $name : 运行中 (PID: $pid)"
|
echo " $name : 运行中 (PID: $pid)"
|
||||||
else
|
else
|
||||||
echo " $name : 已退出"
|
echo " $name : 已退出"
|
||||||
|
|||||||
+33
-3
@@ -16,6 +16,7 @@ readonly SCAN_FIXER_DIR="${SCAN_FIXER_DIR:-$AGV_PROJECT_DIR/scan_fixer}"
|
|||||||
readonly LOG_DIR="${LOG_DIR:-/tmp}"
|
readonly LOG_DIR="${LOG_DIR:-/tmp}"
|
||||||
readonly FASTRTPS_SHM_DIR="${FASTRTPS_SHM_DIR:-/dev/shm}"
|
readonly FASTRTPS_SHM_DIR="${FASTRTPS_SHM_DIR:-/dev/shm}"
|
||||||
readonly AGV_CONTROLLER_DEVICE="${AGV_CONTROLLER_DEVICE:-/dev/agvpro_controller}"
|
readonly AGV_CONTROLLER_DEVICE="${AGV_CONTROLLER_DEVICE:-/dev/agvpro_controller}"
|
||||||
|
readonly UV_BIN="${UV_BIN:-}"
|
||||||
export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-1}"
|
export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-1}"
|
||||||
|
|
||||||
# 日志文件
|
# 日志文件
|
||||||
@@ -131,6 +132,32 @@ ros2_exec() {
|
|||||||
' _ "$ROS_SETUP" "$ROS_WORKSPACE_SETUP" "$ROS_DOMAIN_ID" "$@"
|
' _ "$ROS_SETUP" "$ROS_WORKSPACE_SETUP" "$ROS_DOMAIN_ID" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve_uv_bin() {
|
||||||
|
if [ -n "$UV_BIN" ]; then
|
||||||
|
if [ -x "$UV_BIN" ]; then
|
||||||
|
echo "$UV_BIN"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local candidate
|
||||||
|
candidate=$(command -v uv 2>/dev/null || true)
|
||||||
|
if [ -n "$candidate" ]; then
|
||||||
|
echo "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for candidate in "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv"; do
|
||||||
|
if [ -x "$candidate" ]; then
|
||||||
|
echo "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# ROS2 环境操作
|
# ROS2 环境操作
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -184,8 +211,11 @@ stop_ros2_daemon() {
|
|||||||
pkill -9 -f "ros2-daemon" 2>/dev/null || true
|
pkill -9 -f "ros2-daemon" 2>/dev/null || true
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
source "$ROS_SETUP" 2>/dev/null || true
|
bash -c '
|
||||||
ros2 daemon stop 2>/dev/null || true
|
source "$1" || exit 0
|
||||||
|
export ROS_DOMAIN_ID="$2"
|
||||||
|
ros2 daemon stop
|
||||||
|
' _ "$ROS_SETUP" "$ROS_DOMAIN_ID" 2>/dev/null || true
|
||||||
echo " [OK] ros2 daemon 已重置"
|
echo " [OK] ros2 daemon 已重置"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +253,7 @@ wait_for_nodes() {
|
|||||||
|
|
||||||
while [ "$elapsed" -lt "$max_wait" ]; do
|
while [ "$elapsed" -lt "$max_wait" ]; do
|
||||||
local nodes
|
local nodes
|
||||||
nodes=$(ros2_exec ros2 node list 2>/dev/null || true)
|
nodes=$(ros2_exec timeout 8 ros2 node list --no-daemon --spin-time 3 2>/dev/null || true)
|
||||||
count=$(printf '%s\n' "$nodes" | grep -cE "$pattern" || true)
|
count=$(printf '%s\n' "$nodes" | grep -cE "$pattern" || true)
|
||||||
if [ "$count" -ge "$expected" ]; then
|
if [ "$count" -ge "$expected" ]; then
|
||||||
echo " [OK] 已检测到 $count 个节点"
|
echo " [OK] 已检测到 $count 个节点"
|
||||||
|
|||||||
Reference in New Issue
Block a user