AI实验室后端
This commit is contained in:
+71
-43
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import json
|
||||
import pathlib
|
||||
import uuid
|
||||
from uuid import UUID
|
||||
@@ -6,14 +7,11 @@ from uuid import UUID
|
||||
from fastapi import APIRouter
|
||||
from fastapi import Depends
|
||||
|
||||
from config.emqx import mqtt_publish
|
||||
from config.minIO import get_upload_token
|
||||
from config.redis import redis_client
|
||||
from db.postgres.iot import *
|
||||
from models.BaseResponse import BaseResponse
|
||||
from models.EMQXWebhook import EMQXWebhook
|
||||
from models.IotDeviceCommandRequest import IotDeviceCommandRequest
|
||||
from routers.WS import ws_manager
|
||||
|
||||
iot_router = APIRouter()
|
||||
from config.security import get_user_id_from_token
|
||||
@@ -21,43 +19,44 @@ from config.security import get_user_id_from_token
|
||||
# -------------------- 设备接口 --------------------
|
||||
|
||||
|
||||
@iot_router.post("/common/webhook")
|
||||
async def emqx_webhook(data: EMQXWebhook):
|
||||
device_id = data.clientid
|
||||
event = data.event
|
||||
|
||||
if event == "client.connected":
|
||||
redis_client.set_online(device_id)
|
||||
# 这里刻意等1s 是因为设备连接后这里首先接到通知,但是状态信息设备来没来得及通过mqtt发送来,所以在此等待
|
||||
# 没有直接在mqtt发送来的消息中获取在线状态是因为 这里是通过emqx的webhooks通知的,两种通知方式不同,一方面防止其中一种逻辑失效,另一方面在mqtt消息接收中设置在线状态会存在滞后性,同时也需要设置遗嘱消息,较为
|
||||
await asyncio.sleep(1)
|
||||
await ws_manager.noticeOnlineStatus(
|
||||
{
|
||||
"deviceId": device_id,
|
||||
"online": True,
|
||||
"type": "status",
|
||||
}
|
||||
)
|
||||
|
||||
print(f"[新设备在线] {device_id}")
|
||||
|
||||
elif event == "client.disconnected":
|
||||
redis_client.set_offline(device_id)
|
||||
await ws_manager.noticeOnlineStatus(
|
||||
{
|
||||
"deviceId": device_id,
|
||||
"online": False,
|
||||
"type": "status",
|
||||
}
|
||||
)
|
||||
|
||||
print(f"[设备离线] {device_id}")
|
||||
|
||||
else:
|
||||
# 其他事件直接忽略
|
||||
print(f"[其他事件] {event}")
|
||||
|
||||
return {"ok": True}
|
||||
# 已废弃 Webhooks的离线通知不及时(突然断电断网)
|
||||
# @iot_router.post("/common/webhook")
|
||||
# async def emqx_webhook(data: EMQXWebhook):
|
||||
# device_id = data.clientid
|
||||
# event = data.event
|
||||
#
|
||||
# if event == "client.connected":
|
||||
# redis_client.set_online(device_id)
|
||||
# # 这里刻意等1s 是因为设备连接后这里首先接到通知,但是状态信息设备来没来得及通过mqtt发送来,所以在此等待
|
||||
# # 没有直接在mqtt发送来的消息中获取在线状态是因为 这里是通过emqx的webhooks通知的,两种通知方式不同,一方面防止其中一种逻辑失效,另一方面在mqtt消息接收中设置在线状态会存在滞后性,同时也需要设置遗嘱消息,较为
|
||||
# await asyncio.sleep(1)
|
||||
# await ws_manager.noticeOnlineStatus(
|
||||
# {
|
||||
# "deviceId": device_id,
|
||||
# "online": True,
|
||||
# "type": "status",
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# print(f"[新设备在线] {device_id}")
|
||||
#
|
||||
# elif event == "client.disconnected":
|
||||
# redis_client.set_offline(device_id)
|
||||
# await ws_manager.noticeOnlineStatus(
|
||||
# {
|
||||
# "deviceId": device_id,
|
||||
# "online": False,
|
||||
# "type": "status",
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# print(f"[设备离线] {device_id}")
|
||||
#
|
||||
# else:
|
||||
# # 其他事件直接忽略
|
||||
# print(f"[其他事件] {event}")
|
||||
#
|
||||
# return {"ok": True}
|
||||
|
||||
|
||||
@iot_router.get("/common/device/list")
|
||||
@@ -83,9 +82,9 @@ async def get_device_list(
|
||||
# ===== 👇 核心:补在线状态 =====
|
||||
for d in devices:
|
||||
device_id = d["name"] # 账号
|
||||
d["online"] = redis_client.is_device_online(device_id) == 1
|
||||
|
||||
info_json = redis_client.get_device_info(device_id)
|
||||
d["online"] = info_json.get("online", "0") == "1"
|
||||
d["version"] = info_json.get("version", "")
|
||||
d["ip"] = info_json.get("ip", "")
|
||||
d["hostname"] = info_json.get("hostname", "")
|
||||
@@ -261,6 +260,10 @@ def getUploadUrl(
|
||||
return BaseResponse(data=get_update_package(deviceID))
|
||||
|
||||
|
||||
# request_id -> asyncio.Future
|
||||
pending_commands: dict[str, asyncio.Future] = {}
|
||||
|
||||
|
||||
@iot_router.post("/common/device/command")
|
||||
async def command(
|
||||
data: IotDeviceCommandRequest, user_id: UUID = Depends(get_user_id_from_token)
|
||||
@@ -268,7 +271,32 @@ async def command(
|
||||
if not user_id:
|
||||
return {"error": "userId is required"}
|
||||
|
||||
request_id = str(uuid.uuid4())
|
||||
payload = {"request_id": request_id}
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
future = loop.create_future()
|
||||
pending_commands[request_id] = future
|
||||
|
||||
from config.emqx import mqtt_publish
|
||||
|
||||
await mqtt_publish(
|
||||
data.dept_id, "cmd", data.device_type, data.id, data.command, "{}"
|
||||
data.dept_id,
|
||||
"cmd",
|
||||
data.device_type,
|
||||
data.id,
|
||||
data.command,
|
||||
json.dumps(payload),
|
||||
)
|
||||
return BaseResponse(data=None)
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(future, timeout=5)
|
||||
return BaseResponse(data=result.get("massage"))
|
||||
except asyncio.TimeoutError:
|
||||
return BaseResponse(data=None, message="Device did not respond in time")
|
||||
except asyncio.CancelledError:
|
||||
# 请求被中断,必须清理,但不要吞
|
||||
pending_commands.pop(request_id, None)
|
||||
raise
|
||||
finally:
|
||||
pending_commands.pop(request_id, None)
|
||||
|
||||
Reference in New Issue
Block a user