Files
wrobot/v1.4/python_flask/wrobot_cam.py
T
2025-12-26 23:28:23 +08:00

145 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import threading
import time
import re
import traceback
import paho.mqtt.client as mqtt
from flask import Blueprint, Response, session
from app.glo import *
# ==================== 全局配置 ====================
mqtt_client = None
MQTT_SUB_TOPIC = ""
latest_frame_cache = {}
cache_lock = threading.Lock()
mqtt_sub_lock = threading.Lock()
wrobot_cam = Blueprint('wrobot_cam', __name__)
# ==================== MQTT 核心逻辑 ====================
def on_mqtt_connect(client, userdata, flags, rc):
"""MQTT连接成功回调:此时暂不订阅(等第一次页面访问时再订阅)"""
if rc == 0:
print("MQTT连接成功(暂未订阅任何主题,等待页面访问)")
else:
print(f"MQTT连接失败 错误码: {rc}")
def on_mqtt_message(client, userdata, message):
"""MQTT消息回调:仅处理已订阅的主题"""
try:
if not MQTT_SUB_TOPIC or message.topic != MQTT_SUB_TOPIC:
return
device_id = message.topic.split('/')[0]
if len(message.payload) < 100:
return
with cache_lock:
latest_frame_cache[device_id] = message.payload
except Exception as e:
print(f"MQTT消息处理异常: {e}")
traceback.print_exc()
def start_mqtt_background():
"""启动MQTT客户端(仅连接,不订阅)"""
global mqtt_client
if mqtt_client is not None:
return
mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_mqtt_connect
mqtt_client.on_message = on_mqtt_message
try:
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
mqtt_client.loop_start()
print(f"MQTT客户端已启动 🚀(仅连接,未订阅主题)")
except Exception as e:
print(f"MQTT启动失败: {e}")
traceback.print_exc()
def subscribe_first_topic(new_topic):
"""第一次订阅主题(初始化订阅)"""
global MQTT_SUB_TOPIC
if not mqtt_client or not mqtt_client.is_connected():
raise RuntimeError("MQTT客户端未连接")
with mqtt_sub_lock:
MQTT_SUB_TOPIC = new_topic
mqtt_client.subscribe(MQTT_SUB_TOPIC)
print(f"MQTT首次订阅: {MQTT_SUB_TOPIC}")
def update_mqtt_subscription(new_topic):
"""更新订阅主题(非首次)"""
global MQTT_SUB_TOPIC
if not mqtt_client or not mqtt_client.is_connected():
raise RuntimeError("MQTT客户端未连接")
with mqtt_sub_lock:
# 取消旧主题订阅
mqtt_client.unsubscribe(MQTT_SUB_TOPIC)
print(f"MQTT取消订阅: {MQTT_SUB_TOPIC}")
# 订阅新主题
MQTT_SUB_TOPIC = new_topic
mqtt_client.subscribe(MQTT_SUB_TOPIC)
print(f"MQTT重新订阅: {MQTT_SUB_TOPIC}")
# ==================== Flask 路由核心逻辑 ====================
def generate_video_frame(device_id):
"""生成视频流"""
blank_jpeg = b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xd9'
while True:
try:
with cache_lock:
frame_data = latest_frame_cache.get(device_id, blank_jpeg)
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame_data + b'\r\n')
time.sleep(0.01)
except GeneratorExit:
break
except Exception as e:
print(f"视频流生成异常: {e}")
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + blank_jpeg + b'\r\n')
time.sleep(0.1)
@wrobot_cam.route('/wrobot_cam/<string:device_id>')
@wrobot_cam.route('/wrobot_cam')
def main(device_id=None):
"""核心逻辑:第一次访问时初始化订阅,后续访问时更新订阅"""
# 1. 校验设备ID(无参数时直接报错,因为启动时未知默认设备)
if not device_id:
return Response("必须指定设备ID(访问方式:/wrobot_cam/设备ID)", status=400, mimetype='text/plain')
device_list = GetUserDeviceIdList(session['user']['user_id'])
if device_id not in device_list:
return Response(f"设备ID不合法", status=400, mimetype='text/plain')
# 2. 构建真实主题
new_mqtt_topic = f"{device_id}/head_video"
# 3. 区分「首次订阅」和「更新订阅」
try:
if not MQTT_SUB_TOPIC: # 初始值为空 → 第一次访问,初始化订阅
threading.Thread(target=subscribe_first_topic, args=(new_mqtt_topic,), daemon=True).start()
elif new_mqtt_topic != MQTT_SUB_TOPIC: # 非首次,更新订阅
threading.Thread(target=update_mqtt_subscription, args=(new_mqtt_topic,), daemon=True).start()
except RuntimeError as e:
return Response(f"MQTT客户端异常:{str(e)}", status=500, mimetype='text/plain')
# 4. 返回视频流
return Response(
generate_video_frame(device_id),
mimetype='multipart/x-mixed-replace; boundary=frame'
)
# ==================== 启动初始化 ====================
start_mqtt_background()