add 告警和联调问题修改

This commit is contained in:
HycJack
2026-05-08 17:33:03 +08:00
parent 6eb548ab41
commit 46cc5272f3
8 changed files with 516 additions and 100 deletions

View File

@@ -1,9 +1,15 @@
import asyncio
import json
import time
import asyncio
from typing import Optional, Dict, Callable, Awaitable
from banban.service.device_setting import device_setting_service
from datetime import datetime
from typing import Awaitable, Callable, Dict, Optional
import aiomqtt
from banban.service.binding import BindingService
from banban.service.device_alarm import device_alarm_service
from banban.service.device_setting import device_setting_service
from banban.service.location import location_service
from config import settings
from services.card_service import card_service
from services.offline_audio_cache import offline_audio_cache
@@ -12,6 +18,8 @@ from banban.service.location import location_service
from database.models import ChildLocationCurrent
from datetime import datetime
from services.device_target_cache import device_target_cache
from services.offline_audio_cache import offline_audio_cache
from services.task_manager import task_manager
from utils.logger import session_logger as logger
from services.task_manager import task_manager
@@ -69,30 +77,28 @@ class TalkingQMQTTService:
try:
topic = str(message.topic)
parts = topic.split("/")
if parts[0] == "device":
device_id = parts[1] if len(parts) >= 2 else "unknown"
if not parts or parts[0] != "device":
continue
if not device_id.startswith(f"{self.device_prefix}_"):
continue
device_id = parts[1] if len(parts) >= 2 else "unknown"
if not device_id.startswith(f"{self.device_prefix}_"):
continue
payload = json.loads(message.payload.decode("utf-8"))
msg_id = payload.get("msg_id")
handler = self._msg_handlers.get(msg_id)
if handler:
await handler(device_id, payload)
else:
logger.warning(device_id, "", f"未知 msg_id: {msg_id}, 设备: {device_id}")
except json.JSONDecodeError as e:
logger.error("", "", f"消息解析失败: {e}")
except Exception as e:
logger.error("", "", f"消息处理异常: {e}")
payload = json.loads(message.payload.decode("utf-8"))
msg_id = payload.get("msg_id")
handler = self._msg_handlers.get(msg_id)
if handler:
await handler(device_id, payload)
else:
logger.warning(device_id, "", f"unknown msg_id={msg_id}")
except json.JSONDecodeError as exc:
logger.error("", "", f"invalid mqtt payload: {exc}")
except Exception as exc:
logger.error("", "", f"mqtt message handling failed: {exc}")
except asyncio.CancelledError:
pass
except Exception as e:
logger.error("", "", f"消息循环异常: {e}")
except Exception as exc:
logger.error("", "", f"mqtt loop failed: {exc}")
self._connected = False
async def _schedule_persistence(self, device_id: str, label: str, coro) -> None:
@@ -109,7 +115,6 @@ class TalkingQMQTTService:
)
async def _handle_device_info(self, device_id: str, payload: dict):
# status = payload.get("status")
data = payload.get("data", {})
await self._schedule_persistence(
device_id,
@@ -199,17 +204,15 @@ class TalkingQMQTTService:
async def _handle_nfc_notice_response(self, device_id: str, payload: dict):
status = payload.get("status")
if status == "success":
logger.info(device_id, "", f"[NFC通知] 设备 {device_id} 已收到留言提示")
else:
logger.warning(device_id, "", f"[NFC通知] 设备 {device_id} 留言提示失败: {payload}")
if status != "success":
logger.warning(device_id, "", f"[NFC notice] command failed: {payload}")
async def _handle_nfc_listen_report(self, device_id: str, payload: dict):
params = payload.get("params", {})
nfc_uuid = params.get("uuid")
logger.info(device_id, "", f"[NFC收听] 设备 {device_id} 请求收听留言, UUID={nfc_uuid}")
await self._send_nfc_listen_response(device_id, nfc_uuid)
async def _handle_bind_response(self, device_id: str, payload: dict):
params = payload
status = params.get("status")
@@ -223,7 +226,8 @@ class TalkingQMQTTService:
if result is None:
logger.warning(device_id, "", "[NFC bind] no pending bind session found")
return
logger.info(device_id, "", f"[NFC绑定] 设备 {device_id} 请求绑定卡片, UUID={nfc_uuid}, result={result}")
logger.info(device_id, "", f"[NFC bind] bind completed, uuid={nfc_uuid}, result={result}")
async def _handle_open_response(self, device_id: str, payload: dict):
params = payload
@@ -237,6 +241,25 @@ class TalkingQMQTTService:
status = payload.get("status")
if status == "success":
logger.info(device_id, "", f"[定时休眠] 设备 {device_id} 休眠时间设置成功")
data = payload.get("data", {}) or {}
start = data.get("start")
end = data.get("end")
timezone = data.get("timezone") or "Asia/Shanghai"
if start and end:
try:
await self._schedule_persistence(
device_id,
"sleep_schedule",
device_setting_service.upsert_sleep_schedule(
device_id=device_id,
sleep_mode=1,
disable_time_start=datetime.strptime(start, "%H:%M").time(),
disable_time_end=datetime.strptime(end, "%H:%M").time(),
timezone=timezone,
),
)
except ValueError:
logger.warning(device_id, "", f"[定时休眠] 无法解析设备返回的时间: {payload}")
else:
logger.warning(device_id, "", f"[定时休眠] 设备 {device_id} 休眠时间设置失败: {payload}")
@@ -249,6 +272,11 @@ class TalkingQMQTTService:
async def _handle_alarm_report(self, device_id: str, payload: dict):
logger.info(device_id, "", f"[告警] 设备 {device_id} 发送紧急报警")
await self._schedule_persistence(
device_id,
"alarm_event",
device_alarm_service.record_alarm_event(device_id=device_id, source_msg_id="010"),
)
await self._publish(f"device/{device_id}/event_resp", {"msg_id": "010", "status": "success"})
async def _handle_short_press_message(self, device_id: str, payload: dict):
@@ -268,7 +296,6 @@ class TalkingQMQTTService:
topic = f"device/{device_id}/event_resp"
card = await card_service.get_card_by_uuid(nfc_uuid)
if not card:
logger.warning("", "", f"[NFC收听] 设备 {device_id} 没有找到卡片{nfc_uuid},无法发送留言提示")
payload = {
"msg_id": "005",
"type": 0,
@@ -291,7 +318,8 @@ class TalkingQMQTTService:
}
}
await self._publish(topic, payload)
return
return
if has_pending:
audio_urls = await offline_audio_cache.get_audio_urls(device_id)
# 53D92B6DA20001 测试卡片
@@ -314,49 +342,38 @@ class TalkingQMQTTService:
}
}
else:
params = {}
for k, audio_url in enumerate(audio_urls, start=1):
params[f"url_{k}"] = audio_url
payload = {
"msg_id": "005",
"type": 0,
"params": params
}
params = {f"url_{k}": audio_url for k, audio_url in enumerate(audio_urls, start=1)}
payload = {"msg_id": "005", "type": 0, "params": params}
await offline_audio_cache.clear_audio_urls(device_id)
await self._publish(topic, payload)
else:
payload = {
"msg_id": "005",
"type": 0,
"params": {
"url_1": f"http://{settings.server_host}:{settings.server_port}/assets/audio/no_message.mp3"
}
}
await self._publish(topic, payload)
else:
# 检查卡片是否存在
existing_card = await card_service.get_card_by_uuid(nfc_uuid)
if existing_card:
# 卡片已存在使用卡片绑定的设备ID作为目标设备ID
target_device_id = existing_card.device_id
logger.info(device_id, "card", f"卡片已存在绑定的设备ID: {target_device_id}")
else:
# 卡片不存在,创建新卡片并绑定到当前设备
new_card = await card_service.activate_card(nfc_uuid, device_id)
logger.info(device_id, "card", f"新卡片{nfc_uuid}已创建并激活,绑定到设备: {device_id}")
return
# 设置目标设备
await device_target_cache.set_target(device_id, target_device_id)
payload = {
"msg_id": "005",
"type": 1,
"type": 0,
"params": {
"url_1": f"http://{settings.server_host}:{settings.server_port}/assets/audio/welcome.mp3"
}
"url_1": f"http://{settings.server_host}:{settings.server_port}/assets/audio/no_message.mp3"
},
}
await self._publish(topic, payload)
return
existing_card = await card_service.get_card_by_uuid(nfc_uuid)
if existing_card:
target_device_id = existing_card.device_id
else:
await card_service.activate_card(nfc_uuid, device_id)
return
await device_target_cache.set_target(device_id, target_device_id)
payload = {
"msg_id": "005",
"type": 1,
"params": {
"url_1": f"http://{settings.server_host}:{settings.server_port}/assets/audio/welcome.mp3"
},
}
await self._publish(topic, payload)
async def connect(self):
async with self._connect_lock:
@@ -373,19 +390,15 @@ class TalkingQMQTTService:
)
await self._client.__aenter__()
self._connected = True
logger.system_info("", f"TalkingQ MQTT 已连接: {self.broker}:{self.port}")
await self._client.subscribe("device/+/response", qos=self.qos)
logger.system_info("", "已订阅: device/+/response")
await self._client.subscribe("device/+/event", qos=self.qos)
logger.system_info("", "已订阅: device/+/event")
self._message_task = asyncio.create_task(self._message_loop())
logger.info("", "", "TalkingQ MQTT 服务启动中...")
except Exception as e:
logger.info("", "", f"mqtt connected: {self.broker}:{self.port}")
except Exception as exc:
self._connected = False
logger.error("", "", f"TalkingQ MQTT 连接失败: {e}")
logger.error("", "", f"mqtt connect failed: {exc}")
async def disconnect(self):
if self._message_task and not self._message_task.done():
@@ -412,35 +425,26 @@ class TalkingQMQTTService:
await self._ensure_connected()
try:
await self._client.publish(topic, json.dumps(payload, ensure_ascii=False), qos=self.qos)
logger.info("", "", f"下发命令 -> {topic}: {payload}")
except Exception as e:
logger.error("", "", f"命令下发失败: {e}")
logger.info("", "", f"mqtt publish topic={topic} payload={payload}")
except Exception as exc:
logger.error("", "", f"mqtt publish failed: {exc}")
async def send_gps_query(self, device_id: str) -> str:
topic = f"device/{device_id}/command"
payload = {"msg_id": "001"}
await self._publish(topic, payload)
await self._publish(f"device/{device_id}/command", {"msg_id": "001"})
return "001"
async def send_volume_command(self, device_id: str, level: int) -> str:
topic = f"device/{device_id}/command"
payload = {
"msg_id": "002",
"params": {"level": level}
}
await self._publish(topic, payload)
await self._publish(
f"device/{device_id}/command",
{"msg_id": "002", "params": {"level": level}},
)
return "002"
async def send_ota_command(self, device_id: str, url: str, version: str) -> str:
topic = f"device/{device_id}/command"
payload = {
"msg_id": "003",
"params": {
"url": url,
"version": version,
}
}
await self._publish(topic, payload)
await self._publish(
f"device/{device_id}/command",
{"msg_id": "003", "params": {"url": url, "version": version}},
)
return "003"
async def send_nfc_notice(self, device_id: str, url: str) -> str: