You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
7.4 KiB
196 lines
7.4 KiB
import logging
|
|
import queue
|
|
import sched
|
|
import sys
|
|
import threading
|
|
import time
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from datetime import datetime
|
|
from time import sleep
|
|
|
|
import configSer
|
|
import models.msg
|
|
from upload import mqttHelper
|
|
from upload.RateLimiter import RateLimiter
|
|
|
|
mq_client:mqttHelper.MQTTClient = None
|
|
|
|
# 启动 MQTT 连接线程
|
|
def start_mqtt_connection_thread():
|
|
"""
|
|
启动一个后台线程用于 MQTT 连接
|
|
"""
|
|
mqtt_thread = threading.Thread(target=connect_mqtt_with_retry, args=())
|
|
mqtt_thread.daemon = True # 设置为守护线程,随主线程退出而结束
|
|
mqtt_thread.start()
|
|
|
|
def connect_mqtt_with_retry(max_retries=5, base_delay=10):
|
|
"""
|
|
尝试连接 MQTT 并支持重试机制
|
|
:param max_retries: 最大重试次数
|
|
:param base_delay: 初始延迟时间(秒)
|
|
"""
|
|
is_mqtt_connected=False
|
|
retry_count = 1
|
|
delay = base_delay
|
|
|
|
while not is_mqtt_connected and retry_count <= max_retries:
|
|
try:
|
|
logging.info(f"尝试连接 MQTT... 第 {retry_count} 次")
|
|
if mq_client.start():
|
|
logging.info("MQTT 连接成功!")
|
|
is_mqtt_connected= True
|
|
break
|
|
else:
|
|
logging.info("MQTT 连接失败,稍后重试...")
|
|
except Exception as e:
|
|
logging.info(f"MQTT 通信异常: {e}")
|
|
|
|
retry_count += 1
|
|
sleep(delay)
|
|
|
|
if not mq_client.is_connected():
|
|
logging.info(f"MQTT 连接失败,已达最大重试次数{max_retries}")
|
|
|
|
class DataReporter(threading.Thread):
|
|
def __init__(self,data_fps:int,video_fps:int=0):
|
|
super().__init__()
|
|
self.alert_queue = queue.Queue(maxsize=1) # 数据队列
|
|
self.image_queue = queue.Queue(maxsize=video_fps) # 图片队列
|
|
self.data_queue = queue.Queue(maxsize=data_fps) # 数据队列
|
|
self.image_limiter = RateLimiter(max_rate=video_fps, time_window=1) # 弃用 图片限速: 1张/秒
|
|
self.data_limiter = RateLimiter(max_rate=data_fps, time_window=1) # 数据限速: 30条/秒
|
|
self.running = True
|
|
self.update = False
|
|
self.daemon = True
|
|
self.tcp_callbacks = [] # 存储多个回调函数
|
|
self.mqtt_callbacks = [] # 存储多个回调函数
|
|
self.callback_executor = ThreadPoolExecutor(max_workers=2) # 创建线程池
|
|
self.last_data = None # 存储最后一条数据
|
|
self.last_repeat_counts = 0 # 存储最后一条数据
|
|
|
|
self.scheduler = sched.scheduler(time.time)
|
|
self.schedulerEvent = None
|
|
|
|
|
|
def register_handler(self,handler_fun,conn_type="mqtt"):
|
|
match conn_type:
|
|
case "mqtt":
|
|
self.mqtt_callbacks.append(handler_fun)
|
|
case "tcp":
|
|
self.tcp_callbacks.append(handler_fun)
|
|
case _:
|
|
logging.warn(f"不支持的回调连接类型: {conn_type}")
|
|
def run(self):
|
|
now_timestamp = time.time()
|
|
interval = 1.0 / self.data_limiter.max_rate
|
|
self.schedulerEvent=self.scheduler.enter(interval, 2, self._schedule_data_reporting, (interval,now_timestamp))
|
|
self.scheduler.run()
|
|
|
|
def _schedule_data_reporting(self,interval,now_timestamp):
|
|
if self.running:
|
|
if self.update:
|
|
interval = 1.0 / self.data_limiter.max_rate
|
|
self.update=False
|
|
next_timestamp = now_timestamp+interval
|
|
self.scheduler.enterabs(next_timestamp, 2, self._schedule_data_reporting, (interval,next_timestamp)) # 每 x 秒执行一次
|
|
threading.Thread(target=self.upload, daemon=True).start()
|
|
|
|
def upload(self):
|
|
self._report_data_if_allowed()
|
|
# 告警
|
|
self._report_alert_if_allowed()
|
|
sleep(0.1)
|
|
|
|
def _report_data_if_allowed(self):
|
|
if not self.data_queue.empty():
|
|
data = self.data_queue.get_nowait()
|
|
self.last_data = data
|
|
self.last_repeat_counts = 0
|
|
self._report_data(data, self.last_repeat_counts)
|
|
elif self.last_data is not None and self.last_repeat_counts < 6:
|
|
self.last_repeat_counts += 1
|
|
self._report_data(self.last_data, self.last_repeat_counts)
|
|
|
|
def _report_alert_if_allowed(self):
|
|
# alert上报
|
|
if not self.alert_queue.empty():
|
|
try:
|
|
alert_data = self.alert_queue.get_nowait()
|
|
self._report_alert(alert_data)
|
|
except queue.Empty:
|
|
pass
|
|
|
|
def _execute_callbacks(self, msg_json):
|
|
"""
|
|
执行所有注册的回调函数
|
|
"""
|
|
# logging.info(f"will Reporting to {len(self.tcp_callbacks)} tcp client")
|
|
for callback in self.tcp_callbacks:
|
|
try:
|
|
# 提交到线程池异步执行
|
|
self.callback_executor.submit(callback, msg_json)
|
|
except Exception as e:
|
|
print(f"Error executing callback {callback.__name__}: {e}")
|
|
def _execute_mqtt_callbacks(self, msg_json):
|
|
for callback in self.mqtt_callbacks:
|
|
try:
|
|
# 提交到线程池异步执行
|
|
self.callback_executor.submit(callback, msg_json)
|
|
except Exception as e:
|
|
print(f"Error executing callback {callback.__name__}: {e}")
|
|
def _report_alert(self, data):
|
|
print(f"Reporting alert, timestamp: {data[0]}")
|
|
# 这里替换为实际的上报代码
|
|
msg = models.msg.Msg(_from="dev", cmd="alert", values=data[1])
|
|
msg_json = msg.to_json()
|
|
self._execute_mqtt_callbacks(msg_json)
|
|
self._execute_callbacks(msg_json)
|
|
|
|
def _report_image(self, data):
|
|
# 实现图片上报逻辑
|
|
print(f"Reporting image, timestamp: {data[0]}")
|
|
# 这里替换为实际的上报代码
|
|
msg = models.msg.Msg(_from="dev", cmd="image", values=data[1])
|
|
msg_json = msg.to_json()
|
|
self._execute_callbacks(msg_json)
|
|
|
|
def _report_data(self, data, last_repeat_counts=0):
|
|
# 实际的上报代码,数据结构转换
|
|
msg=models.msg.Msg(_from="dev",cmd="data",values=data[1])
|
|
# 重新格式化时间,数据等间隔
|
|
msg.values.time=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
logging.info(f"Reporting [{last_repeat_counts}] data: {data}")
|
|
msg_json=msg.to_json()
|
|
self._execute_mqtt_callbacks(msg_json)
|
|
self._execute_callbacks(msg_json)
|
|
|
|
def stop(self):
|
|
self.running = False
|
|
self.join()
|
|
|
|
def adjust_rate(self, new_rate, data_type='data'):
|
|
with self.data_limiter.lock:
|
|
self.data_limiter.max_rate = new_rate
|
|
self.data_queue = queue.Queue(maxsize=new_rate)
|
|
self.update=True
|
|
self.data_limiter.update_interval()
|
|
|
|
def adjust_mqtt(self, conf: configSer.Upload) -> bool:
|
|
global mq_client
|
|
self.mqtt_callbacks=[]
|
|
if mq_client is not None and mq_client.is_connected():
|
|
mq_client.stop()
|
|
|
|
if conf.enable:
|
|
mq_client = mqttHelper.MQTTClient(**conf.mqtt)
|
|
try:
|
|
if mq_client.start():
|
|
self.register_handler(mq_client.publish,"mqtt")
|
|
else:
|
|
logging.warn("mq链接失败,不上报")
|
|
return False
|
|
except Exception as e:
|
|
logging.warn("adjust_mqtt 通讯异常", e)
|
|
sys.exit(0)
|
|
return True
|