214 lines
5.8 KiB
Python
214 lines
5.8 KiB
Python
from typing import Any
|
|
import socketio
|
|
import hashlib
|
|
import hmac
|
|
import base64
|
|
import json
|
|
import cv2
|
|
import asyncio
|
|
from datetime import datetime
|
|
|
|
|
|
class WSClient:
|
|
__SHA256KEY = b"smart-parking-platform"
|
|
sio = socketio.AsyncClient(reconnection=False)
|
|
wsServerUrl = None
|
|
rtmpServerUrl = None
|
|
|
|
connected = False
|
|
ready = False
|
|
|
|
async def onReady(self):
|
|
pass
|
|
|
|
username = None
|
|
password = None
|
|
cameraID = None
|
|
|
|
rtspUrl = None
|
|
alarmRules = None
|
|
"""
|
|
@type alarmRule: [{
|
|
'id': 1,
|
|
'name': '测试规则',
|
|
'enabled': True,
|
|
'algorithmType':'body',
|
|
'triggerDayOfWeek': [1, 2, 3, 4, 5],
|
|
'triggerTimeStart': '09:00:00',
|
|
'triggerTimeEnd': '18:00:00',
|
|
'triggerCountMin': 0,
|
|
'triggerCountMax': -1
|
|
}]
|
|
"""
|
|
|
|
alarmThrottle = {}
|
|
"""
|
|
@type alarmThrottle: {
|
|
[ alarmRuleID:str ]: datetime
|
|
}
|
|
"""
|
|
|
|
throttleTime = 60 # seconds
|
|
|
|
context = {}
|
|
|
|
def __init__(
|
|
self,
|
|
wsServerUrl: str,
|
|
rtmpServerUrl: str,
|
|
username: str,
|
|
password: str,
|
|
cameraID: str,
|
|
onReady: Any,
|
|
context: dict = {},
|
|
) -> None:
|
|
self.wsServerUrl = wsServerUrl
|
|
self.rtmpServerUrl = rtmpServerUrl
|
|
self.username = username
|
|
self.password = base64.b64encode(
|
|
hmac.new(
|
|
self.__SHA256KEY, password.encode("utf-8"), hashlib.sha256
|
|
).digest()
|
|
).decode("utf-8")
|
|
self.cameraID = cameraID
|
|
self.onReady = onReady
|
|
self.context = context
|
|
|
|
self.sio.on("cameraConfigChange", self.onCameraConfigChange)
|
|
|
|
self.sio.on("connect", self.onConnect)
|
|
self.sio.on("disconnect", self.onDisconnect)
|
|
self.sio.on("connect_error", self.onConnectError)
|
|
|
|
print(f"ws client for camera {cameraID} created")
|
|
pass
|
|
|
|
async def connect(self):
|
|
if self.connected:
|
|
return
|
|
data = json.dumps(
|
|
{
|
|
"username": self.username,
|
|
"password": self.password,
|
|
"cameraID": self.cameraID,
|
|
}
|
|
)
|
|
await self.sio.connect(
|
|
self.wsServerUrl, socketio_path="/ws/ai/", headers={"data": data}
|
|
)
|
|
self.connected = True
|
|
pass
|
|
|
|
async def sendMessage(self, msg):
|
|
await self.sio.emit("message", msg)
|
|
print("message sent")
|
|
pass
|
|
|
|
def matchAlarmRule(self, data):
|
|
"""
|
|
@type data: {
|
|
'algorithmType': 'body',
|
|
'count': 1,
|
|
'predictResult': Any
|
|
}
|
|
"""
|
|
matchedRules = []
|
|
if not self.ready:
|
|
raise Exception(f"CameraID {self.cameraID} not ready or disconnected")
|
|
if self.alarmRules is None:
|
|
return matchedRules
|
|
for rule in self.alarmRules:
|
|
if (
|
|
rule["enabled"]
|
|
and rule["algorithmType"] == data["algorithmType"]
|
|
and (datetime.now().weekday() + 1) in rule["triggerDayOfWeek"]
|
|
and datetime.now().time()
|
|
>= datetime.strptime(rule["triggerTimeStart"], "%H:%M:%S").time()
|
|
and datetime.now().time()
|
|
<= datetime.strptime(rule["triggerTimeEnd"], "%H:%M:%S").time()
|
|
and data["count"] >= rule["triggerCountMin"]
|
|
and (
|
|
data["count"] <= rule["triggerCountMax"]
|
|
or rule["triggerCountMax"] == -1
|
|
)
|
|
and (
|
|
self.alarmThrottle.get(rule["id"]) is None
|
|
or (datetime.now() - self.alarmThrottle[rule["id"]]).seconds
|
|
> self.throttleTime
|
|
)
|
|
):
|
|
matchedRules.append(rule)
|
|
return matchedRules
|
|
|
|
async def trySendAlarm(self, data):
|
|
"""
|
|
@type data: {
|
|
'algorithmType': 'body',
|
|
'count': 1,
|
|
'predictResult': Any
|
|
}
|
|
"""
|
|
rules = self.matchAlarmRule(data)
|
|
for rule in rules:
|
|
"""
|
|
AlarmData: {
|
|
picBase64: string;
|
|
alarmRuleID: number;
|
|
}
|
|
"""
|
|
await self.sio.emit(
|
|
"alarm",
|
|
{
|
|
"alarmRuleID": rule["id"],
|
|
"picBase64": "data:image/jpg;base64,"
|
|
+ base64.b64encode(
|
|
cv2.imencode(".jpg", data["predictResult"].plot())[1]
|
|
).decode("utf-8"),
|
|
},
|
|
)
|
|
# 防止短时间内重复发送
|
|
self.alarmThrottle[rule["id"]] = datetime.now()
|
|
print(f"camera {self.cameraID} alarm sent: {rule['name']}")
|
|
pass
|
|
|
|
pass
|
|
|
|
async def disconnect(self):
|
|
await self.sio.disconnect()
|
|
pass
|
|
|
|
async def onCameraConfigChange(self, data):
|
|
if self.rtspUrl is not None and self.rtspUrl != data["rtspUrl"]:
|
|
print(f"Camera {self.cameraID} rtsp url changed, disconnecting...")
|
|
await self.disconnect()
|
|
return
|
|
|
|
self.rtspUrl = data["rtspUrl"]
|
|
self.alarmRules = data["alarmRules"]
|
|
print(f"Camera {self.cameraID} config updated")
|
|
|
|
if self.connected and not self.ready:
|
|
self.ready = True
|
|
asyncio.get_event_loop().create_task(self.onReady(self))
|
|
|
|
pass
|
|
|
|
async def stayConnected(self):
|
|
await self.connect()
|
|
await self.sio.wait()
|
|
pass
|
|
|
|
def onConnect(self):
|
|
print(f"connection for camera {self.cameraID} established")
|
|
pass
|
|
|
|
def onDisconnect(self):
|
|
print(f"camera {self.cameraID} disconnected from server")
|
|
self.connected = False
|
|
self.ready = False
|
|
pass
|
|
|
|
def onConnectError(self, err):
|
|
print(err)
|
|
pass
|