fix: 管理后台连接状态 + 修复按钮无响应
连接状态(正式): - /api/display/state 扩展:screens_online(大屏心跳统计,opc/display/heartbeat)+ last_command(最近指令与发布结果) - 前端大屏每 10s 上报心跳(稳定 client_id),后端 30s TTL 统计在线大屏数 - 管理台新增「连接状态」卡片:后端/MQTT/在线大屏/最近指令,3s 轮询刷新 按钮无响应根治: - admin.js 改为 document 事件委托(data-nav/data-control/data-action/复制/预览),不再依赖逐个绑定 - /static 与 /admin 响应加 no-cache 头,杜绝旧 JS/CSS 缓存导致的按钮失效 - SSE 不再自动重载页面(避免与操作回调双重重载清掉反馈);立即播放改为 toast 不重载
This commit is contained in:
+42
-3
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""MQTT 发布中心 —— 后端 → 前端控制通道
|
||||
页面控制 / 媒体控制 / 卡片展示指令统一走 opc/display/command
|
||||
数据快照按 DPM_MQTT_TICK_INTERVAL 推送 opc/dashboard/tick
|
||||
"""MQTT 发布/订阅中心 —— 后端 ↔ 前端控制通道
|
||||
发布:opc/display/command(页面/媒体/卡片控制)、opc/dashboard/tick(数据快照)
|
||||
订阅:opc/display/heartbeat(大屏在线心跳,统计在线大屏数)
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -27,6 +27,8 @@ class MqttHub:
|
||||
self.client = None
|
||||
self.connected = False
|
||||
self._lock = threading.RLock()
|
||||
self._screens = {} # client_id -> last_seen_ts
|
||||
self.last_command = None # {action, params, ts, published}
|
||||
|
||||
# ---------- 生命周期 ----------
|
||||
def start(self):
|
||||
@@ -43,6 +45,7 @@ class MqttHub:
|
||||
self.client.username_pw_set(settings.MQTT_USERNAME, settings.MQTT_PASSWORD)
|
||||
self.client.on_connect = self._on_connect
|
||||
self.client.on_disconnect = self._on_disconnect
|
||||
self.client.on_message = self._on_message
|
||||
self.client.connect_async(settings.MQTT_HOST, settings.MQTT_PORT, keepalive=30)
|
||||
self.client.loop_start()
|
||||
log.info("MQTT 连接中 %s:%s ...", settings.MQTT_HOST, settings.MQTT_PORT)
|
||||
@@ -62,6 +65,7 @@ class MqttHub:
|
||||
if rc == 0:
|
||||
self.connected = True
|
||||
log.info("MQTT 已连接 %s:%s", settings.MQTT_HOST, settings.MQTT_PORT)
|
||||
client.subscribe(settings.TOPIC_HEARTBEAT, qos=0)
|
||||
else:
|
||||
log.warning("MQTT 连接失败 rc=%s", rc)
|
||||
|
||||
@@ -70,6 +74,34 @@ class MqttHub:
|
||||
if rc != 0:
|
||||
log.warning("MQTT 断开(rc=%s),自动重连中...", rc)
|
||||
|
||||
def _on_message(self, client, userdata, msg):
|
||||
"""接收大屏心跳:记录 client_id 与时间"""
|
||||
if msg.topic == settings.TOPIC_HEARTBEAT:
|
||||
try:
|
||||
payload = json.loads(msg.payload.decode("utf-8"))
|
||||
cid = payload.get("client_id") or msg.topic
|
||||
with self._lock:
|
||||
self._screens[cid] = time.time()
|
||||
# 清理过期
|
||||
cutoff = time.time() - settings.SCREEN_TTL
|
||||
self._screens = {k: v for k, v in self._screens.items() if v > cutoff}
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
|
||||
# ---------- 状态查询 ----------
|
||||
def screens_online(self):
|
||||
with self._lock:
|
||||
cutoff = time.time() - settings.SCREEN_TTL
|
||||
return sum(1 for v in self._screens.values() if v > cutoff)
|
||||
|
||||
def status(self):
|
||||
return {
|
||||
"mqtt_connected": self.connected,
|
||||
"mqtt_host": f"{settings.MQTT_HOST}:{settings.MQTT_PORT}",
|
||||
"screens_online": self.screens_online(),
|
||||
"last_command": self.last_command,
|
||||
}
|
||||
|
||||
# ---------- 发布 ----------
|
||||
def publish(self, topic, payload, qos=1, retain=False):
|
||||
if not self.client or not self.connected:
|
||||
@@ -93,6 +125,13 @@ class MqttHub:
|
||||
}
|
||||
ok = self.publish(settings.TOPIC_COMMAND, payload)
|
||||
payload["published"] = ok
|
||||
with self._lock:
|
||||
self.last_command = {
|
||||
"action": action,
|
||||
"params": params or {},
|
||||
"ts": payload["ts"],
|
||||
"published": ok,
|
||||
}
|
||||
if ok:
|
||||
bus.emit(payload) # SSE 兼容通道(仅发布成功时推送)
|
||||
return payload
|
||||
|
||||
Reference in New Issue
Block a user