二维码位置配置
This commit is contained in:
+15
-5
@@ -1130,14 +1130,14 @@ def api_camera_capture():
|
|||||||
|
|
||||||
@app.route("/api/camera/arm_refresh")
|
@app.route("/api/camera/arm_refresh")
|
||||||
def api_arm_camera_refresh():
|
def api_arm_camera_refresh():
|
||||||
"""从机械臂拉一张 JPEG(流式读第一个完整帧,超时则降级)"""
|
"""从机械臂拉一张 JPEG(流式读第一个完整帧)"""
|
||||||
import requests
|
import requests
|
||||||
try:
|
try:
|
||||||
r = requests.get(ARM_CAMERA_CONFIG["url"], stream=True, timeout=5)
|
r = requests.get(ARM_CAMERA_CONFIG["url"], stream=True, timeout=8)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
return "", 404
|
return "", 404
|
||||||
data = b""
|
data = b""
|
||||||
for chunk in r.iter_content(chunk_size=4096):
|
for chunk in r.iter_content(chunk_size=8192):
|
||||||
data += chunk
|
data += chunk
|
||||||
# 在累积数据中找 JPEG 完整帧
|
# 在累积数据中找 JPEG 完整帧
|
||||||
s = data.find(b"\xff\xd8")
|
s = data.find(b"\xff\xd8")
|
||||||
@@ -1145,8 +1145,8 @@ def api_arm_camera_refresh():
|
|||||||
if s >= 0 and e > s:
|
if s >= 0 and e > s:
|
||||||
r.close()
|
r.close()
|
||||||
return Response(data[s:e+2], mimetype="image/jpeg")
|
return Response(data[s:e+2], mimetype="image/jpeg")
|
||||||
# 数据太长还没找到 JPEG 也直接返回(可能是空流)
|
# 累积超过 5MB 还没找到完整帧,说明流异常,放弃
|
||||||
if len(data) > 1024 * 1024:
|
if len(data) > 5 * 1024 * 1024:
|
||||||
r.close()
|
r.close()
|
||||||
return "", 404
|
return "", 404
|
||||||
r.close()
|
r.close()
|
||||||
@@ -1460,6 +1460,16 @@ if __name__ == "__main__":
|
|||||||
gs.qr_scanner = QRScanner(CAMERA_CONFIG["device_index"])
|
gs.qr_scanner = QRScanner(CAMERA_CONFIG["device_index"])
|
||||||
gs.camera_opened = gs.qr_scanner.open()
|
gs.camera_opened = gs.qr_scanner.open()
|
||||||
logger.info(f"AGV 摄像头初始化: {'成功' if gs.camera_opened else '失败'}")
|
logger.info(f"AGV 摄像头初始化: {'成功' if gs.camera_opened else '失败'}")
|
||||||
|
# 启动时自动检测机械臂摄像头
|
||||||
|
try:
|
||||||
|
import requests as _startup_req
|
||||||
|
r = _startup_req.get(ARM_CAMERA_CONFIG["url"], stream=True, timeout=5)
|
||||||
|
gs.arm_camera_opened = (r.status_code == 200)
|
||||||
|
r.close()
|
||||||
|
logger.info(f"机械臂摄像头检测: {'成功' if gs.arm_camera_opened else '失败'}")
|
||||||
|
except Exception as _e:
|
||||||
|
gs.arm_camera_opened = False
|
||||||
|
logger.warning(f"机械臂摄像头检测失败: {_e}")
|
||||||
app.run(
|
app.run(
|
||||||
host=SERVER_CONFIG["host"],
|
host=SERVER_CONFIG["host"],
|
||||||
port=SERVER_CONFIG["port"],
|
port=SERVER_CONFIG["port"],
|
||||||
|
|||||||
+31
-22
@@ -35,39 +35,47 @@ ARM_CAMERA_INDEX = 0 # 机械臂端摄像头设备号
|
|||||||
_ffmpeg_proc = None
|
_ffmpeg_proc = None
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_ffmpeg():
|
||||||
|
"""确保 ffmpeg 进程在运行,自动重启崩溃的进程"""
|
||||||
|
global _ffmpeg_proc
|
||||||
|
if _ffmpeg_proc is None or _ffmpeg_proc.poll() is not None:
|
||||||
|
logger.info(f"启动 ffmpeg 视频流 (Video{ARM_CAMERA_INDEX})")
|
||||||
|
_ffmpeg_proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
"ffmpeg",
|
||||||
|
"-f", "v4l2",
|
||||||
|
"-input_format", "mjpeg",
|
||||||
|
"-i", f"/dev/video{ARM_CAMERA_INDEX}",
|
||||||
|
"-vf", "rotate=PI",
|
||||||
|
"-q:v", "8",
|
||||||
|
"-f", "mjpeg",
|
||||||
|
"-"
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
time.sleep(2.0) # 等待 ffmpeg 初始化完成(设备冷启动可能需要更久)
|
||||||
|
|
||||||
|
|
||||||
@arm_video_app.route("/api/camera/preview")
|
@arm_video_app.route("/api/camera/preview")
|
||||||
def arm_camera_preview():
|
def arm_camera_preview():
|
||||||
"""机械臂摄像头 MJPEG 流 (ffmpeg)"""
|
"""机械臂摄像头 MJPEG 流 (ffmpeg)"""
|
||||||
global _ffmpeg_proc
|
_ensure_ffmpeg()
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
global _ffmpeg_proc
|
global _ffmpeg_proc
|
||||||
# 启动 ffmpeg 进程(如果尚未运行)
|
|
||||||
if _ffmpeg_proc is None or _ffmpeg_proc.poll() is not None:
|
|
||||||
logger.info(f"启动 ffmpeg 视频流 (Video{ARM_CAMERA_INDEX})")
|
|
||||||
_ffmpeg_proc = subprocess.Popen(
|
|
||||||
[
|
|
||||||
"ffmpeg",
|
|
||||||
"-f", "v4l2",
|
|
||||||
"-input_format", "mjpeg",
|
|
||||||
"-i", f"/dev/video{ARM_CAMERA_INDEX}",
|
|
||||||
"-vf", "rotate=PI",
|
|
||||||
"-q:v", "8",
|
|
||||||
"-f", "mjpeg",
|
|
||||||
"-"
|
|
||||||
],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
time.sleep(0.5) # 等待 ffmpeg 初始化
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
if _ffmpeg_proc is None or _ffmpeg_proc.poll() is not None:
|
if _ffmpeg_proc is None or _ffmpeg_proc.poll() is not None:
|
||||||
break
|
_ensure_ffmpeg()
|
||||||
jpeg = _ffmpeg_proc.stdout.read(65536)
|
jpeg = _ffmpeg_proc.stdout.read(65536)
|
||||||
if not jpeg:
|
if not jpeg:
|
||||||
break
|
# ffmpeg 无数据输出,重启它
|
||||||
|
logger.warning("ffmpeg stdout 空,重启")
|
||||||
|
_ffmpeg_proc.terminate()
|
||||||
|
_ffmpeg_proc = None
|
||||||
|
_ensure_ffmpeg()
|
||||||
|
continue
|
||||||
yield (b"--frame\r\nContent-Type: image/jpeg\r\n\r\n" + jpeg + b"\r\n")
|
yield (b"--frame\r\nContent-Type: image/jpeg\r\n\r\n" + jpeg + b"\r\n")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"视频流异常: {e}")
|
logger.error(f"视频流异常: {e}")
|
||||||
@@ -92,6 +100,7 @@ def arm_camera_restart():
|
|||||||
if _ffmpeg_proc:
|
if _ffmpeg_proc:
|
||||||
_ffmpeg_proc.terminate()
|
_ffmpeg_proc.terminate()
|
||||||
_ffmpeg_proc = None
|
_ffmpeg_proc = None
|
||||||
|
_ensure_ffmpeg()
|
||||||
return jsonify({"ok": True})
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user