-
This commit is contained in:
+28
-6
@@ -126,10 +126,11 @@ try:
|
|||||||
# Flask 2.3+ 方式
|
# Flask 2.3+ 方式
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
load_persisted_config()
|
load_persisted_config()
|
||||||
# 启动时自动重连 AGV(异步,不阻塞 Flask 启动)
|
# 启动时自动连接所有设备(异步,不阻塞 Flask 启动)
|
||||||
import threading
|
import threading
|
||||||
def _auto_reconnect():
|
def _auto_connect_all():
|
||||||
time.sleep(2) # 等待 Flask 完全就绪
|
time.sleep(2) # 等待 Flask 完全就绪
|
||||||
|
# 连接 AGV
|
||||||
try:
|
try:
|
||||||
from utils.agv_controller_ros2 import AGVController
|
from utils.agv_controller_ros2 import AGVController
|
||||||
gs.agv_controller = AGVController()
|
gs.agv_controller = AGVController()
|
||||||
@@ -139,7 +140,19 @@ try:
|
|||||||
print("[启动] AGV 自动连接失败,请手动连接")
|
print("[启动] AGV 自动连接失败,请手动连接")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[启动] AGV 自动连接异常: {e}")
|
print(f"[启动] AGV 自动连接异常: {e}")
|
||||||
threading.Thread(target=_auto_reconnect, daemon=True).start()
|
# 连接机械臂
|
||||||
|
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()
|
||||||
|
gs.arm_client.state_on()
|
||||||
|
print("[启动] 机械臂自动连接成功")
|
||||||
|
else:
|
||||||
|
print("[启动] 机械臂自动连接失败,请手动连接")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[启动] 机械臂自动连接异常: {e}")
|
||||||
|
threading.Thread(target=_auto_connect_all, daemon=True).start()
|
||||||
except:
|
except:
|
||||||
# 兼容旧版 Flask
|
# 兼容旧版 Flask
|
||||||
@app.before_first_request
|
@app.before_first_request
|
||||||
@@ -184,6 +197,15 @@ def api_status():
|
|||||||
if gs.agv_controller:
|
if gs.agv_controller:
|
||||||
agv_connected = gs.agv_controller.is_connected()
|
agv_connected = gs.agv_controller.is_connected()
|
||||||
|
|
||||||
|
# 实时检测机械臂摄像头是否可用
|
||||||
|
try:
|
||||||
|
import requests as _armcam_req
|
||||||
|
_r = _armcam_req.get(ARM_CAMERA_CONFIG["url"], stream=True, timeout=3)
|
||||||
|
gs.arm_camera_opened = (_r.status_code == 200)
|
||||||
|
_r.close()
|
||||||
|
except:
|
||||||
|
gs.arm_camera_opened = False
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"state": gs.state,
|
"state": gs.state,
|
||||||
"agv_connected": agv_connected,
|
"agv_connected": agv_connected,
|
||||||
@@ -1179,7 +1201,7 @@ def api_agv_move():
|
|||||||
"""控制 AGV 移动(前进/后退/左转/右转/停止)"""
|
"""控制 AGV 移动(前进/后退/左转/右转/停止)"""
|
||||||
data = request.json
|
data = request.json
|
||||||
direction = data.get("direction", "stop") # forward / backward / left / right / stop
|
direction = data.get("direction", "stop") # forward / backward / left / right / stop
|
||||||
speed = data.get("speed", AGV_CONFIG.get("move_speed", 0.5))
|
speed = data.get("speed", AGV_CONFIG.get("move_speed", 1.0))
|
||||||
if not gs.agv_controller or not gs.agv_controller.is_connected():
|
if not gs.agv_controller or not gs.agv_controller.is_connected():
|
||||||
return jsonify({"ok": False, "error": "AGV 未连接"}), 400
|
return jsonify({"ok": False, "error": "AGV 未连接"}), 400
|
||||||
try:
|
try:
|
||||||
@@ -1250,8 +1272,8 @@ def api_mission_start():
|
|||||||
"qr_scan": bool(data.get("qr_scan", True)),
|
"qr_scan": bool(data.get("qr_scan", True)),
|
||||||
"front_photo": bool(data.get("front_photo", True)),
|
"front_photo": bool(data.get("front_photo", True)),
|
||||||
"back_photo": bool(data.get("back_photo", True)),
|
"back_photo": bool(data.get("back_photo", True)),
|
||||||
"agv_speed": float(data.get("agv_speed", 0.5)),
|
"agv_speed": float(data.get("agv_speed", 1.0)),
|
||||||
"arm_speed": int(data.get("arm_speed", 500)),
|
"arm_speed": int(data.get("arm_speed", 1000)),
|
||||||
}
|
}
|
||||||
print(f"[Mission] options: {options}")
|
print(f"[Mission] options: {options}")
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -14,8 +14,8 @@ ARM_HOST = "192.168.60.88"
|
|||||||
AGV_CONFIG = {
|
AGV_CONFIG = {
|
||||||
"device": "/dev/agvpro_controller",
|
"device": "/dev/agvpro_controller",
|
||||||
"baudrate": 10000000,
|
"baudrate": 10000000,
|
||||||
"move_speed": 0.5,
|
"move_speed": 1.0,
|
||||||
"turn_speed": 0.5,
|
"turn_speed": 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ========== 机械臂 TCP 客户端 ==========
|
# ========== 机械臂 TCP 客户端 ==========
|
||||||
@@ -77,7 +77,7 @@ JOINT_LIMITS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ========== 机械臂默认速度 ==========
|
# ========== 机械臂默认速度 ==========
|
||||||
DEFAULT_ARM_SPEED = 500
|
DEFAULT_ARM_SPEED = 1000
|
||||||
|
|
||||||
# ========== 状态定义 ==========
|
# ========== 状态定义 ==========
|
||||||
class State:
|
class State:
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ createApp({
|
|||||||
frontPhotoEnabled: true,
|
frontPhotoEnabled: true,
|
||||||
backPhotoEnabled: true,
|
backPhotoEnabled: true,
|
||||||
// 速度控制
|
// 速度控制
|
||||||
agvSpeed: 0.5,
|
agvSpeed: 1.0,
|
||||||
armSpeed: 500,
|
armSpeed: 1000,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -63,10 +63,10 @@ createApp({
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(API + '/api/status')
|
const res = await fetch(API + '/api/status')
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (!this.armCameraOpened) {
|
const opened = data.arm_camera_opened
|
||||||
this.armPreviewUrl = ''
|
if (opened !== this.armCameraOpened || (opened && !this.armPreviewUrl)) {
|
||||||
} else if (!this.armPreviewUrl) {
|
this.armCameraOpened = opened
|
||||||
this.armPreviewUrl = API + '/api/camera/arm_preview'
|
this.armPreviewUrl = opened ? API + '/api/camera/arm_preview' : ''
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
@@ -94,6 +94,9 @@ createApp({
|
|||||||
if (data.point_status) this.pointStatus = data.point_status
|
if (data.point_status) this.pointStatus = data.point_status
|
||||||
if (data.machine_status) this.machineStatus = data.machine_status
|
if (data.machine_status) this.machineStatus = data.machine_status
|
||||||
this.armCameraOpened = data.arm_camera_opened
|
this.armCameraOpened = data.arm_camera_opened
|
||||||
|
if (this.armCameraOpened && !this.armPreviewUrl) {
|
||||||
|
this.armPreviewUrl = API + '/api/camera/arm_preview'
|
||||||
|
}
|
||||||
|
|
||||||
// 错误弹窗
|
// 错误弹窗
|
||||||
if (data.waiting_error) {
|
if (data.waiting_error) {
|
||||||
@@ -260,7 +263,12 @@ createApp({
|
|||||||
e.target.style.display = 'none'
|
e.target.style.display = 'none'
|
||||||
},
|
},
|
||||||
onArmPreviewError(e) {
|
onArmPreviewError(e) {
|
||||||
e.target.style.display = 'none'
|
// 重新加载:加时间戳避免缓存
|
||||||
|
const url = this.armPreviewUrl
|
||||||
|
if (url) {
|
||||||
|
const sep = url.includes('?') ? '&' : '?'
|
||||||
|
this.armPreviewUrl = url + sep + '_t=' + Date.now()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// ===== 网格任务显示方法 =====
|
// ===== 网格任务显示方法 =====
|
||||||
getPointStatus(pr, c) {
|
getPointStatus(pr, c) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ createApp({
|
|||||||
agvConnected: false,
|
agvConnected: false,
|
||||||
agvBattery: null,
|
agvBattery: null,
|
||||||
agvPosition: null,
|
agvPosition: null,
|
||||||
agvSpeed: 0.5,
|
agvSpeed: 1.0,
|
||||||
agvMoveInterval: null,
|
agvMoveInterval: null,
|
||||||
agvCameraUrl: API + '/api/camera/refresh',
|
agvCameraUrl: API + '/api/camera/refresh',
|
||||||
agvCameraTimer: null,
|
agvCameraTimer: null,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class AGVController:
|
|||||||
if rc != 0:
|
if rc != 0:
|
||||||
logger.warning(f"发布 cmd_vel 失败: {err}")
|
logger.warning(f"发布 cmd_vel 失败: {err}")
|
||||||
|
|
||||||
def move_forward(self, speed: float = 0.5, duration: float = None):
|
def move_forward(self, speed: float = 1.0, duration: float = None):
|
||||||
"""前进"""
|
"""前进"""
|
||||||
if not self.is_connected():
|
if not self.is_connected():
|
||||||
return
|
return
|
||||||
@@ -85,7 +85,7 @@ class AGVController:
|
|||||||
time.sleep(duration)
|
time.sleep(duration)
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def move_backward(self, speed: float = 0.5, duration: float = None):
|
def move_backward(self, speed: float = 1.0, duration: float = None):
|
||||||
"""后退"""
|
"""后退"""
|
||||||
if not self.is_connected():
|
if not self.is_connected():
|
||||||
return
|
return
|
||||||
@@ -94,7 +94,7 @@ class AGVController:
|
|||||||
time.sleep(duration)
|
time.sleep(duration)
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def turn_left(self, speed: float = 0.5, duration: float = None):
|
def turn_left(self, speed: float = 1.0, duration: float = None):
|
||||||
"""左转"""
|
"""左转"""
|
||||||
if not self.is_connected():
|
if not self.is_connected():
|
||||||
return
|
return
|
||||||
@@ -103,7 +103,7 @@ class AGVController:
|
|||||||
time.sleep(duration)
|
time.sleep(duration)
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def turn_right(self, speed: float = 0.5, duration: float = None):
|
def turn_right(self, speed: float = 1.0, duration: float = None):
|
||||||
"""右转"""
|
"""右转"""
|
||||||
if not self.is_connected():
|
if not self.is_connected():
|
||||||
return
|
return
|
||||||
@@ -112,7 +112,7 @@ class AGVController:
|
|||||||
time.sleep(duration)
|
time.sleep(duration)
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def move_left_lateral(self, speed: float = 0.5, duration: float = None):
|
def move_left_lateral(self, speed: float = 1.0, duration: float = None):
|
||||||
"""向左横向移动"""
|
"""向左横向移动"""
|
||||||
if not self.is_connected():
|
if not self.is_connected():
|
||||||
return
|
return
|
||||||
@@ -121,7 +121,7 @@ class AGVController:
|
|||||||
time.sleep(duration)
|
time.sleep(duration)
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def move_right_lateral(self, speed: float = 0.5, duration: float = None):
|
def move_right_lateral(self, speed: float = 1.0, duration: float = None):
|
||||||
"""向右横向移动"""
|
"""向右横向移动"""
|
||||||
if not self.is_connected():
|
if not self.is_connected():
|
||||||
return
|
return
|
||||||
@@ -176,7 +176,7 @@ class AGVController:
|
|||||||
return self._position
|
return self._position
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def go_to_point(self, x: float, y: float, rz: float = None, speed: float = 0.5) -> bool:
|
def go_to_point(self, x: float, y: float, rz: float = None, speed: float = 1.0) -> bool:
|
||||||
"""移动到目标点(需要 ROS2 导航栈)"""
|
"""移动到目标点(需要 ROS2 导航栈)"""
|
||||||
logger.warning("go_to_point 需要 ROS2 Nav2 支持,当前仅记录目标")
|
logger.warning("go_to_point 需要 ROS2 Nav2 支持,当前仅记录目标")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ ARM_HOST = "192.168.60.88"
|
|||||||
AGV_CONFIG = {
|
AGV_CONFIG = {
|
||||||
"device": "/dev/agvpro_controller",
|
"device": "/dev/agvpro_controller",
|
||||||
"baudrate": 10000000,
|
"baudrate": 10000000,
|
||||||
"move_speed": 0.5,
|
"move_speed": 1.0,
|
||||||
"turn_speed": 0.5,
|
"turn_speed": 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ========== 机械臂 TCP 客户端 ==========
|
# ========== 机械臂 TCP 客户端 ==========
|
||||||
@@ -77,7 +77,7 @@ JOINT_LIMITS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ========== 机械臂默认速度 ==========
|
# ========== 机械臂默认速度 ==========
|
||||||
DEFAULT_ARM_SPEED = 500
|
DEFAULT_ARM_SPEED = 1000
|
||||||
|
|
||||||
# ========== 状态定义 ==========
|
# ========== 状态定义 ==========
|
||||||
class State:
|
class State:
|
||||||
|
|||||||
@@ -101,11 +101,10 @@ class MissionExecutorV3:
|
|||||||
self._nav = Nav2Navigator()
|
self._nav = Nav2Navigator()
|
||||||
|
|
||||||
# 速度控制(默认值,可在 execute_mission 时覆写)
|
# 速度控制(默认值,可在 execute_mission 时覆写)
|
||||||
self.arm_speed = 500
|
self.arm_speed = 1000
|
||||||
self.agv_speed = 0.5
|
self.agv_speed = 1.0
|
||||||
|
|
||||||
# 照片上传序号计数器(连续递增,从1开始)
|
# 照片上传序号计数器(连续递增,从1开始)
|
||||||
self.next_upload_index = 1
|
|
||||||
|
|
||||||
# ==================== 连接 ====================
|
# ==================== 连接 ====================
|
||||||
|
|
||||||
@@ -239,7 +238,6 @@ class MissionExecutorV3:
|
|||||||
self._log(f"📍 点位蛇形路径: {len(path)} 个点位, {total_machines} 台机器")
|
self._log(f"📍 点位蛇形路径: {len(path)} 个点位, {total_machines} 台机器")
|
||||||
|
|
||||||
# 重置照片上传序号(每次任务开始时重置,从1开始)
|
# 重置照片上传序号(每次任务开始时重置,从1开始)
|
||||||
self.next_upload_index = 1
|
|
||||||
|
|
||||||
# 任务步骤控制开关
|
# 任务步骤控制开关
|
||||||
if options is None:
|
if options is None:
|
||||||
@@ -258,8 +256,8 @@ class MissionExecutorV3:
|
|||||||
has_arm_pose = self.arm_client and any(abs(a) > 0.01 for a in arm_initial_pose)
|
has_arm_pose = self.arm_client and any(abs(a) > 0.01 for a in arm_initial_pose)
|
||||||
|
|
||||||
# 速度控制(从前端传入)
|
# 速度控制(从前端传入)
|
||||||
self.arm_speed = int(options.get("arm_speed", 500))
|
self.arm_speed = int(options.get("arm_speed", 1000))
|
||||||
self.agv_speed = float(options.get("agv_speed", 0.5))
|
self.agv_speed = float(options.get("agv_speed", 1.0))
|
||||||
self._log(f"🚀 AGV速度={self.agv_speed:.1f}m/s, 机械臂速度={self.arm_speed}")
|
self._log(f"🚀 AGV速度={self.agv_speed:.1f}m/s, 机械臂速度={self.arm_speed}")
|
||||||
|
|
||||||
# 设置 Nav2 导航速度(仅在任务开始时设一次)
|
# 设置 Nav2 导航速度(仅在任务开始时设一次)
|
||||||
@@ -831,9 +829,8 @@ class MissionExecutorV3:
|
|||||||
self.arm_client.set_angles(angles, speed=self.arm_speed)
|
self.arm_client.set_angles(angles, speed=self.arm_speed)
|
||||||
self._wait_arm_ready(angles)
|
self._wait_arm_ready(angles)
|
||||||
|
|
||||||
# 拍照(upload_index 连续递增)
|
# 拍照(每台机器独立从0开始编号)
|
||||||
path = self._capture_arm_photo(row, col, side, pi + 1, qr_value, upload_index=self.next_upload_index)
|
path = self._capture_arm_photo(row, col, side, pi + 1, qr_value, upload_index=pi + 1)
|
||||||
self.next_upload_index += 1
|
|
||||||
if path:
|
if path:
|
||||||
self._log(f" 💾 {os.path.basename(path)}")
|
self._log(f" 💾 {os.path.basename(path)}")
|
||||||
|
|
||||||
@@ -842,7 +839,7 @@ class MissionExecutorV3:
|
|||||||
upload_index: int = 0) -> Optional[str]:
|
upload_index: int = 0) -> Optional[str]:
|
||||||
"""从机械臂摄像头拍照,直接上传到服务器(不保存本地)
|
"""从机械臂摄像头拍照,直接上传到服务器(不保存本地)
|
||||||
|
|
||||||
upload_index: 从1开始,先正面后背面,由调用方维护
|
upload_index: 每台机器独立,从0开始
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
resp = requests.get(ARM_CAMERA_SNAPSHOT, timeout=10)
|
resp = requests.get(ARM_CAMERA_SNAPSHOT, timeout=10)
|
||||||
@@ -892,6 +889,32 @@ class MissionExecutorV3:
|
|||||||
self._log(f" ❌ 上传异常 [{index}]: {e}")
|
self._log(f" ❌ 上传异常 [{index}]: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _upload_photo_bytes(self, filename: str, image_data: bytes, serial_number: str, index: int) -> bool:
|
||||||
|
"""上传照片 bytes 到远程服务器(不保存本地文件)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: 文件名(用于服务器存储)
|
||||||
|
image_data: 图片二进制数据
|
||||||
|
serial_number: 二维码/序列号
|
||||||
|
index: 上传序号(从1开始递增)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2tleSI6ImZhNTNkZTZiLWE3NjYtNDZmNC05MDUyLTQ2MjUzZTAyNjdmNSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.lC4vKThZo4aAOLsekm2kPgaEJRqRx-YDQWKfHFqxdPNESCKy57l3eIqaKTj2ZjAMaoYAwYlMrv5M1zAOJsO_PA"
|
||||||
|
}
|
||||||
|
files = {"file": (filename, image_data, "image/jpeg")}
|
||||||
|
data = {"serialNumber": serial_number, "index": str(index)}
|
||||||
|
resp = requests.post(UPLOAD_URL, files=files, data=data, headers=headers, timeout=30)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
self._log(f" ☁️ 上传成功 [{index}]: {filename}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self._log(f" ⚠️ 上传失败 [{index}] HTTP {resp.status_code}: {resp.text[:200]}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f" ❌ 上传异常 [{index}]: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
# ==================== 控制 ====================
|
# ==================== 控制 ====================
|
||||||
|
|
||||||
def _wait_pause(self):
|
def _wait_pause(self):
|
||||||
|
|||||||
+49
-13
@@ -254,19 +254,7 @@ class AGVCommandServer:
|
|||||||
def _connect_roboflow(self):
|
def _connect_roboflow(self):
|
||||||
self.roboflow = RoboFlowClient()
|
self.roboflow = RoboFlowClient()
|
||||||
if self.roboflow.connect():
|
if self.roboflow.connect():
|
||||||
logger.info("RoboFlow 连接成功")
|
logger.info("RoboFlow 连接成功(上电由 power_on_arm() 完成)")
|
||||||
# 连接成功后自动上电并激活机械臂
|
|
||||||
time.sleep(1)
|
|
||||||
try:
|
|
||||||
resp = self.roboflow.send_recv("power_on()")
|
|
||||||
logger.info(f"机械臂上电: {resp}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"机械臂上电失败: {e}")
|
|
||||||
try:
|
|
||||||
resp = self.roboflow.send_recv("state_on()")
|
|
||||||
logger.info(f"机械臂激活: {resp}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"机械臂激活失败: {e}")
|
|
||||||
else:
|
else:
|
||||||
logger.warning("RoboFlow 连接失败,服务将以 limited 模式运行")
|
logger.warning("RoboFlow 连接失败,服务将以 limited 模式运行")
|
||||||
|
|
||||||
@@ -342,13 +330,61 @@ class AGVCommandServer:
|
|||||||
|
|
||||||
|
|
||||||
# ========== 入口 ==========
|
# ========== 入口 ==========
|
||||||
|
import time
|
||||||
|
|
||||||
|
def power_on_arm(max_retries: int = 5) -> bool:
|
||||||
|
"""通过 ElephantRobot 给机械臂上电并激活(带重试)"""
|
||||||
|
from pymycobot import ElephantRobot
|
||||||
|
|
||||||
|
for attempt in range(1, max_retries + 1):
|
||||||
|
try:
|
||||||
|
logger.info(f"正在通过 ElephantRobot 连接 RoboFlow (尝试 {attempt}/{max_retries})...")
|
||||||
|
el = ElephantRobot("127.0.0.1", 5001)
|
||||||
|
el.start_client()
|
||||||
|
logger.info("ElephantRobot start_client 成功,等待2秒...")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
el._power_on()
|
||||||
|
logger.info("power_on 指令已发送,等待2秒...")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
el.start_robot()
|
||||||
|
logger.info("start_robot 指令已发送,等待5秒...")
|
||||||
|
time.sleep(5)
|
||||||
|
logger.info("✅ 机械臂上电+激活 全部完成")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠️ 第 {attempt} 次尝试失败: {e}")
|
||||||
|
if attempt < max_retries:
|
||||||
|
logger.info(f"等待 3 秒后重试...")
|
||||||
|
time.sleep(3)
|
||||||
|
else:
|
||||||
|
logger.error(f"❌ 所有 {max_retries} 次尝试均失败,将以 limited 模式运行")
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
# 先通过 ElephantRobot 给机械臂上电并激活
|
||||||
|
power_on_arm()
|
||||||
|
|
||||||
server = AGVCommandServer(port=5002)
|
server = AGVCommandServer(port=5002)
|
||||||
|
|
||||||
# 启动 Flask 视频流服务(端口 5003)
|
# 启动 Flask 视频流服务(端口 5003)
|
||||||
|
from werkzeug.serving import make_server
|
||||||
|
arm_server_http = None
|
||||||
|
for attempt in range(5):
|
||||||
|
try:
|
||||||
arm_server_http = make_server("0.0.0.0", 5003, arm_video_app, threaded=True)
|
arm_server_http = make_server("0.0.0.0", 5003, arm_video_app, threaded=True)
|
||||||
|
break
|
||||||
|
except OSError as e:
|
||||||
|
if attempt < 4 and "Address already in use" in str(e):
|
||||||
|
logger.warning(f"端口 5003 被占用(第{attempt+1}次),等待...")
|
||||||
|
time.sleep(3)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
http_thread = threading.Thread(target=arm_server_http.serve_forever, daemon=True)
|
http_thread = threading.Thread(target=arm_server_http.serve_forever, daemon=True)
|
||||||
http_thread.start()
|
http_thread.start()
|
||||||
logger.info("机械臂视频流服务已启动: http://0.0.0.0:5003")
|
logger.info("机械臂视频流服务已启动: http://0.0.0.0:5003")
|
||||||
|
|||||||
Reference in New Issue
Block a user