264 lines
7.7 KiB
Python
264 lines
7.7 KiB
Python
from flask import Flask, render_template, Response, jsonify
|
||
import time, json, socket, os, logging, struct
|
||
import mmap
|
||
import threading
|
||
import subprocess
|
||
import atexit
|
||
import fcntl
|
||
import setproctitle
|
||
setproctitle.setproctitle("wust_vision_web")
|
||
|
||
app = Flask(__name__)
|
||
|
||
# ===============================
|
||
# 参数设置:选择模式
|
||
# ===============================
|
||
# True -> 强制共享内存模式
|
||
# False -> 文件模式
|
||
USE_SHARED_MEMORY_MODE = True
|
||
STREAM_FPS = 60
|
||
FRAME_INTERVAL = 1.0 / STREAM_FPS
|
||
# 通信参数
|
||
shared_memory_path = "/dev/shm/debug_frame"
|
||
shared_size = 2 * 1024 * 1024 # 2MB
|
||
shared_frame_path = "/dev/shm/debug_frame.jpg"
|
||
|
||
# 初始化通信模式
|
||
use_shared_memory = False
|
||
mapfile = None
|
||
fd = None
|
||
|
||
# 权限修复锁
|
||
permission_lock = threading.Lock()
|
||
|
||
port = 8000
|
||
def ensure_shared_memory_permissions():
|
||
"""确保共享内存文件存在且权限正确"""
|
||
with permission_lock:
|
||
try:
|
||
if not os.path.exists(shared_memory_path):
|
||
print(f"创建共享内存文件: {shared_memory_path}")
|
||
with open(shared_memory_path, "wb") as f:
|
||
f.write(b"\0" * shared_size)
|
||
|
||
current_mode = oct(os.stat(shared_memory_path).st_mode & 0o777)
|
||
if current_mode != "0o777":
|
||
print(f"修复权限 (当前: {current_mode} -> 目标: 777)")
|
||
result = subprocess.run(
|
||
["sudo", "chmod", "777", shared_memory_path],
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
if result.returncode == 0:
|
||
print("权限修复成功")
|
||
return True
|
||
else:
|
||
print(f"权限修复失败: {result.stderr.strip()}")
|
||
return False
|
||
return True
|
||
except Exception as e:
|
||
print(f"权限修复异常: {str(e)}")
|
||
return False
|
||
|
||
|
||
def init_shared_memory():
|
||
"""初始化共享内存连接"""
|
||
global use_shared_memory, mapfile, fd
|
||
|
||
if not ensure_shared_memory_permissions():
|
||
print("[WARN] 权限修复失败")
|
||
use_shared_memory = False
|
||
return False
|
||
|
||
try:
|
||
fd = os.open(shared_memory_path, os.O_RDONLY)
|
||
mapfile = mmap.mmap(fd, shared_size, mmap.MAP_SHARED, mmap.PROT_READ)
|
||
fcntl.flock(fd, fcntl.LOCK_SH | fcntl.LOCK_NB)
|
||
use_shared_memory = True
|
||
print("[INFO] 共享内存初始化成功")
|
||
return True
|
||
except Exception as e:
|
||
print(f"[WARN] 共享内存初始化失败: {e}")
|
||
if mapfile:
|
||
try:
|
||
mapfile.close()
|
||
except:
|
||
pass
|
||
mapfile = None
|
||
if fd:
|
||
try:
|
||
os.close(fd)
|
||
except:
|
||
pass
|
||
fd = None
|
||
use_shared_memory = False
|
||
return False
|
||
|
||
|
||
# ===============================
|
||
# 初始化模式
|
||
# ===============================
|
||
if USE_SHARED_MEMORY_MODE:
|
||
if init_shared_memory():
|
||
print("✅ 使用共享内存模式")
|
||
else:
|
||
print("⚠️ 强制共享内存模式失败,回退到文件模式")
|
||
use_shared_memory = False
|
||
else:
|
||
use_shared_memory = False
|
||
print("ℹ️ 使用文件模式")
|
||
|
||
|
||
# ===============================
|
||
# 清理函数
|
||
# ===============================
|
||
@atexit.register
|
||
def cleanup():
|
||
if mapfile:
|
||
try:
|
||
mapfile.close()
|
||
except:
|
||
pass
|
||
if fd:
|
||
try:
|
||
os.close(fd)
|
||
except:
|
||
pass
|
||
|
||
|
||
# ===============================
|
||
# MJPEG 流生成器
|
||
# ===============================
|
||
def mjpeg_stream():
|
||
global use_shared_memory, mapfile
|
||
last_fix_attempt = 0
|
||
|
||
while True:
|
||
try:
|
||
if use_shared_memory and mapfile:
|
||
try:
|
||
mapfile.seek(0)
|
||
size_bytes = mapfile.read(4)
|
||
if len(size_bytes) < 4:
|
||
time.sleep(FRAME_INTERVAL)
|
||
continue
|
||
jpg_size = struct.unpack("I", size_bytes)[0]
|
||
if jpg_size <= 0 or jpg_size > shared_size - 4:
|
||
time.sleep(FRAME_INTERVAL)
|
||
continue
|
||
jpg_bytes = mapfile.read(jpg_size)
|
||
if len(jpg_bytes) != jpg_size:
|
||
time.sleep(FRAME_INTERVAL)
|
||
continue
|
||
if jpg_bytes[0:3] != b"\xff\xd8\xff":
|
||
time.sleep(FRAME_INTERVAL)
|
||
continue
|
||
except (OSError, ValueError) as e:
|
||
current_time = time.time()
|
||
if current_time - last_fix_attempt > 60:
|
||
print("尝试重新初始化共享内存...")
|
||
if init_shared_memory():
|
||
continue
|
||
last_fix_attempt = current_time
|
||
use_shared_memory = False
|
||
continue
|
||
|
||
if not use_shared_memory or not mapfile:
|
||
try:
|
||
with open(shared_frame_path, "rb") as f:
|
||
jpg_bytes = f.read()
|
||
if jpg_bytes[0:3] != b"\xff\xd8\xff":
|
||
time.sleep(FRAME_INTERVAL)
|
||
continue
|
||
except FileNotFoundError:
|
||
time.sleep(0.1)
|
||
continue
|
||
except Exception:
|
||
time.sleep(0.1)
|
||
continue
|
||
|
||
yield b"--frame\r\n" b"Content-Type: image/jpeg\r\n\r\n" + jpg_bytes + b"\r\n"
|
||
time.sleep(FRAME_INTERVAL)
|
||
except Exception:
|
||
time.sleep(0.5)
|
||
|
||
|
||
# ===============================
|
||
# Flask 路由
|
||
# ===============================
|
||
@app.route("/")
|
||
def index():
|
||
def get_local_ip():
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
try:
|
||
s.connect(("10.255.255.255", 1))
|
||
IP = s.getsockname()[0]
|
||
except:
|
||
IP = "127.0.0.1"
|
||
finally:
|
||
s.close()
|
||
return IP
|
||
|
||
url = f"http://{get_local_ip()}:{port}"
|
||
return render_template("index.html", server_url=url)
|
||
|
||
|
||
@app.route("/video")
|
||
def video_feed():
|
||
return Response(
|
||
mjpeg_stream(), mimetype="multipart/x-mixed-replace; boundary=frame"
|
||
)
|
||
|
||
|
||
@app.route("/data")
|
||
def get_data():
|
||
try:
|
||
with open("/dev/shm/cmd_log.json", "r") as f:
|
||
return jsonify(json.load(f))
|
||
except Exception as e:
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
@app.route("/serial_log")
|
||
def serial_log():
|
||
try:
|
||
with open("/dev/shm/serial_log.json", "r") as f:
|
||
return jsonify(json.load(f))
|
||
except Exception as e:
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
@app.route("/target_log")
|
||
def target_log():
|
||
try:
|
||
with open("/dev/shm/target_log.json", "r") as f:
|
||
return jsonify(json.load(f))
|
||
except Exception as e:
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
# ===============================
|
||
# 主函数
|
||
# ===============================
|
||
if __name__ == "__main__":
|
||
logging.basicConfig(level=logging.INFO)
|
||
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
||
|
||
def get_local_ip():
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
try:
|
||
s.connect(("10.255.255.255", 1))
|
||
IP = s.getsockname()[0]
|
||
except:
|
||
IP = "127.0.0.1"
|
||
finally:
|
||
s.close()
|
||
return IP
|
||
|
||
url = f"http://{get_local_ip()}:{port}"
|
||
print(f"✅ Web 调试器已启动: {url}")
|
||
print(f" - 共享内存模式: {'是' if use_shared_memory else '否'}")
|
||
print(f" - 访问地址: {url}")
|
||
|
||
app.run(host="0.0.0.0", port=port, threaded=True)
|