199 lines
7.7 KiB
Python
199 lines
7.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
import sys
|
||
import os
|
||
import io
|
||
import time
|
||
from pathlib import Path
|
||
|
||
# ========== 修复点1:安全重定向标准流(避免直接覆盖导致关闭) ==========
|
||
def safe_redirect_stdio():
|
||
"""安全重定向stdout/stderr为UTF-8,兼容流未初始化/已关闭场景"""
|
||
try:
|
||
# 仅在流可用时重定向,使用原始buffer避免递归关闭
|
||
if sys.stdout and not sys.stdout.closed:
|
||
sys.stdout = io.TextIOWrapper(
|
||
sys.stdout.buffer,
|
||
encoding='utf-8',
|
||
line_buffering=True, # 行缓冲避免数据滞留
|
||
write_through=True # 立即写入避免句柄阻塞
|
||
)
|
||
if sys.stderr and not sys.stderr.closed:
|
||
sys.stderr = io.TextIOWrapper(
|
||
sys.stderr.buffer,
|
||
encoding='utf-8',
|
||
line_buffering=True,
|
||
write_through=True
|
||
)
|
||
except Exception as e:
|
||
# 重定向失败时使用原始stdout输出,避免崩溃
|
||
print(f"标准流重定向警告: {e}", file=sys.__stdout__)
|
||
|
||
# 先执行安全重定向(放在导入前,避免导入时输出乱码)
|
||
safe_redirect_stdio()
|
||
|
||
# ========== 修复点2:规范导入(避免重复/顺序错误) ==========
|
||
from loguru import logger
|
||
from midware.ui.main_ui import start_main_ui
|
||
from midware.config.base_config import (
|
||
init_base_config,
|
||
update_device_config,
|
||
get_device_config
|
||
)
|
||
from midware.config.excel_config import load_csv_config
|
||
from midware.core.error_check import check_checksum, verify_frame_integrity
|
||
from midware.network.udp_handler import MultiUDPServer
|
||
from midware.network.protocol_dispatcher import dispatch_udp_data
|
||
import midware.config.base_config as base_config
|
||
|
||
# 添加工程根目录到环境变量(确保配置文件路径正确)
|
||
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.append(PROJECT_ROOT)
|
||
# 修正配置文件路径为绝对路径(避免相对路径导致文件找不到)
|
||
CSV_CONFIG_PATH = os.path.join(PROJECT_ROOT, "config_files/device_config.csv")
|
||
|
||
# ========== 修复点3:初始化日志(避免日志写入触发流错误) ==========
|
||
def init_logger():
|
||
"""初始化日志,使用原始stdout避免重定向流关闭问题"""
|
||
# 清空默认日志配置
|
||
logger.remove()
|
||
# 确保日志目录存在
|
||
Path("logs").mkdir(exist_ok=True)
|
||
|
||
# 运行日志
|
||
logger.add(
|
||
"logs/run_{time:YYYY-MM-DD}.log",
|
||
rotation="daily",
|
||
level="INFO",
|
||
format="{time} | {level} | {module}:{line} | {message}",
|
||
enqueue=True, # 异步写入避免句柄阻塞
|
||
catch=True # 捕获日志写入异常
|
||
)
|
||
# 错误日志
|
||
logger.add(
|
||
"logs/error_{time:YYYY-MM-DD}.log",
|
||
rotation="daily",
|
||
level="ERROR",
|
||
format="{time} | {level} | {module}:{line} | {message}",
|
||
enqueue=True,
|
||
catch=True
|
||
)
|
||
# 控制台输出(关键:使用原始stdout,避免重定向流关闭报错)
|
||
logger.add(
|
||
sys.__stdout__,
|
||
level="INFO",
|
||
format="{time} | {level} | {module}:{line} | {message}",
|
||
enqueue=True,
|
||
catch=True
|
||
)
|
||
return logger
|
||
|
||
# ========== 主程序逻辑(核心修复:结构清晰+资源安全释放) ==========
|
||
if __name__ == "__main__":
|
||
# 初始化日志(最先执行,避免后续报错无日志)
|
||
log = init_logger()
|
||
log.info("虚拟仿真平台中间层软件启动")
|
||
logger.info("========== 虚拟仿真平台中间层软件 启动 ==========")
|
||
|
||
# ========== 修复点4:安全加载CSV配置(核心解决load_csv_config报错) ==========
|
||
'''
|
||
try:
|
||
# 加载CSV配置(使用绝对路径,避免文件找不到导致流异常)
|
||
DEVICE_CONFIG_DICT = load_csv_config(CSV_CONFIG_PATH)
|
||
if not DEVICE_CONFIG_DICT:
|
||
logger.error(f"CSV配置文件加载为空!路径:{CSV_CONFIG_PATH}")
|
||
sys.exit(1)
|
||
logger.info(f"CSV配置加载成功|设备数量:{len(DEVICE_CONFIG_DICT)}")
|
||
except Exception as e:
|
||
logger.error(f"加载CSV配置失败:{str(e)}", exc_info=True)
|
||
sys.exit(1)
|
||
'''
|
||
# 初始化基础配置
|
||
try:
|
||
init_base_config()
|
||
device_config = get_device_config()
|
||
if not device_config:
|
||
logger.error("基础配置加载为空,工程无法启动!")
|
||
sys.exit(1)
|
||
logger.info("基础配置初始化成功")
|
||
except Exception as e:
|
||
logger.error(f"基础配置初始化失败:{str(e)}", exc_info=True)
|
||
sys.exit(1)
|
||
|
||
# 检查外设配置
|
||
if not base_config.DEVICE_CONFIG_DICT:
|
||
logger.error("外设配置加载失败,工程启动终止")
|
||
sys.exit(1)
|
||
else:
|
||
logger.info("外设配置加载成功")
|
||
# 改用logger输出,避免print触发流错误
|
||
logger.info("程序运行到这里了")
|
||
|
||
# 实例化UDP服务(避免global关键字,减少生命周期问题)
|
||
udp_server = None
|
||
try:
|
||
udp_server = MultiUDPServer()
|
||
logger.info(
|
||
f"工程启动成功|外设数量:{len(device_config)}|"
|
||
f"UDP监听端口:{list(udp_server.udp_sockets.keys()) if hasattr(udp_server, 'udp_sockets') else []}"
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"UDP服务初始化失败:{str(e)}", exc_info=True)
|
||
sys.exit(1)
|
||
|
||
# 主循环:处理UDP数据
|
||
try:
|
||
# 启动主UI(如需启用,取消注释)
|
||
# start_main_ui()
|
||
|
||
logger.info("进入UDP数据处理主循环...")
|
||
while True:
|
||
if udp_server is None:
|
||
break
|
||
|
||
# 非阻塞接收UDP数据
|
||
recv_data = udp_server.recv_multi_udp_hex()
|
||
if recv_data:
|
||
for data in recv_data:
|
||
# 安全取值,避免KeyError
|
||
port = data.get("port", "")
|
||
dev_name = data.get("dev_name", "")
|
||
protocol = data.get("protocol", "")
|
||
raw_data = data.get("raw_data", b"")
|
||
|
||
if len(raw_data) > 0:
|
||
# 改用logger输出,避免print操作关闭的流
|
||
logger.info(f"接收到 {dev_name} 的原始数据:{raw_data}, 协议类型:{protocol}")
|
||
logger.info("后续根据设备的协议类型进行数据解析和转发...")
|
||
|
||
# 协议类型判断
|
||
if protocol == "UART":
|
||
pass
|
||
|
||
# 分发处理数据(修正:传入单条数据,而非整个列表)
|
||
# dispatch_udp_data([data])
|
||
|
||
# 回传数据到故障注入软件
|
||
udp_server.send_to_fault(raw_data, dev_name)
|
||
# 微休眠,降低CPU占用
|
||
time.sleep(1e-6)
|
||
|
||
except KeyboardInterrupt:
|
||
logger.info("用户终止工程,开始释放资源...")
|
||
except Exception as e:
|
||
logger.error(f"主循环异常:{str(e)}", exc_info=True)
|
||
finally:
|
||
# ========== 修复点5:安全释放资源(避免重复关闭/空对象调用) ==========
|
||
try:
|
||
# 关闭UDP服务(仅当对象存在且未关闭时)
|
||
if udp_server and hasattr(udp_server, "close"):
|
||
udp_server.close()
|
||
logger.info("UDP服务已关闭")
|
||
except Exception as e:
|
||
logger.warning(f"UDP服务关闭警告:{str(e)}")
|
||
|
||
# 刷新并关闭日志(避免日志句柄泄漏)
|
||
logger.info("========== 虚拟仿真平台中间层软件 退出 ==========")
|
||
logger.remove() # 清理所有日志句柄
|
||
|
||
# 安全退出
|
||
sys.exit(0) |