first commit
This commit is contained in:
0
__init__.py
Normal file
0
__init__.py
Normal file
BIN
config_files/chip_config.xlsx
Normal file
BIN
config_files/chip_config.xlsx
Normal file
Binary file not shown.
4
config_files/device_config - 20260210.csv
Normal file
4
config_files/device_config - 20260210.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
名称,协议类型,内存基地址,内存偏移地址,UDP端口,说明,发送缓存区大小,接收缓存区大小
|
||||||
|
Uart0,UART,0x20800000,0x0000,8880,光纤陀螺A,512,512
|
||||||
|
CAN1,CAN,0x20810000,0x0100,8881,姿态传感器,1024,1024
|
||||||
|
1553B0,1553B,0x20820000,0x0200,8882,测控模块,2048,2048
|
||||||
|
12
config_files/device_config - 20260420.csv
Normal file
12
config_files/device_config - 20260420.csv
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
名称,编号,协议类型,内存基地址,内存偏移地址,port,remote_port,说明,发送缓存区大小,接收缓存区大小
|
||||||
|
光纤陀螺A,1,UART,0x20800000,0x0000,4000,11000,Uart0,512,512
|
||||||
|
反作用轮A,2,CAN,0x20810000,0x0100,4001,11011,CAN0,1024,1024
|
||||||
|
1553B0,3,1553B,0x20820000,0x0200,4002,11022,测控模块,2048,2048
|
||||||
|
电压采集,4,AD,0x20830000,0x0300,4003,11033,AD0,512,512
|
||||||
|
开关量输出,1,OC,0x20840000,0x0400,4004,11044,OC0,512,512
|
||||||
|
光纤陀螺B,1,UART,0x20850000,0X0001,4005,11055,UART1,512,512
|
||||||
|
星敏A,1,CAN,0x20860000,0x0101,4006,11066,CAN1,512,512
|
||||||
|
数传A,1,1553B,0x20870000,0x0201,4007,11077,1553B1,512,512
|
||||||
|
adc1,1,AD,0x21800000,0x0488,4018,11088,正电压采集热敏量采集,512,512
|
||||||
|
继电器输出,2,OC,0x20890000,0x0401,4009,11099,OC1,512,512
|
||||||
|
uart24,24,UART,0x20800c80,0x0000,3021,3071,地测串口,1024,2048
|
||||||
|
12
config_files/device_config-20260413.csv
Normal file
12
config_files/device_config-20260413.csv
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
名称,协议类型,内存基地址,内存偏移地址,port,说明,发送缓存区大小,接收缓存区大小
|
||||||
|
光纤陀螺A,UART,0x20800000,0x0000,4000,Uart0,512,512
|
||||||
|
反作用轮A,CAN,0x20810000,0x0100,4001,CAN0,1024,1024
|
||||||
|
1553B0,1553B,0x20820000,0x0200,4002,测控模块,2048,2048
|
||||||
|
电压采集,AD,0x20830000,0x0300,4003,AD0,512,512
|
||||||
|
开关量输出,OC,0x20840000,0x0400,4004,OC0,512,512
|
||||||
|
光纤陀螺B,UART,0x20850000,0X0001,4005,UART1,512,512
|
||||||
|
星敏A,CAN,0x20860000,0x0101,4006,CAN1,512,512
|
||||||
|
数传A,1553B,0x20870000,0x0201,4007,1553B1,512,512
|
||||||
|
adc1,AD,0x21800000,0x0488,4018,正电压采集热敏量采集,512,512
|
||||||
|
继电器输出,OC,0x20890000,0x0401,4009,OC1,512,512
|
||||||
|
uart24,UART,0x20800c80,0x0000,4030,地测串口,1024,2048
|
||||||
|
22
config_files/device_config.csv
Normal file
22
config_files/device_config.csv
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
名称,编号,协议类型,内存基地址,内存偏移地址,port,remote_port,tcp_local_port,tcp_bus_port,说明,发送缓存区大小,接收缓存区大小
|
||||||
|
光纤陀螺A,1,UART,0x20800000,0x0000,4000,11000,,,Uart0,512,512
|
||||||
|
反作用轮A,2,CAN,0x20810000,0x0100,4001,11011,,,CAN0,1024,1024
|
||||||
|
1553B0,3,1553B,0x20820000,0x0200,4002,11022,,,测控模块,2048,2048
|
||||||
|
电压采集,4,AD,0x20830000,0x0300,4003,11033,,,AD0,512,512
|
||||||
|
开关量输出,1,OC,0x20840000,0x0400,4004,11044,,,OC0,512,512
|
||||||
|
光纤陀螺B,1,UART,0x20850000,0X0001,4005,11055,,,UART1,512,512
|
||||||
|
星敏A,3,CAN,0x20860000,0x0101,4006,11066,,,CAN1,512,512
|
||||||
|
数传A,1,1553B,0x20870000,0x0201,4007,11077,,,1553B1,512,512
|
||||||
|
adc1,1,AD,0x21800000,0x0488,4018,11088,,,正电压采集热敏量采集,512,512
|
||||||
|
继电器输出,2,OC,0x20890000,0x0401,4009,11099,,,OC1,512,512
|
||||||
|
uart24,24,UART,0x20800c80,0x0000,13045,13095,13024,12024,地测串口,1024,2048
|
||||||
|
uart26,26,UART,0x20C00000,0x0000,3145,3195,13026,12026,高速通信,2048,512
|
||||||
|
uart0,0,UART,0x20C00001,0x0001,3021,3071,,,陀螺A,512,512
|
||||||
|
uart1,1,UART,0x20C00002,0x0002,3022,3072,,,陀螺B,512,512
|
||||||
|
uart2,2,UART,0x20C00003,0x0003,3023,3073,,,陀螺C,512,512
|
||||||
|
uart3,3,UART,0x20C00004,0x0004,3024,3074,,,预留,512,512
|
||||||
|
uart18,18,UART,0x20C00005,0x0005,3039,3089,,,星敏C,512,512
|
||||||
|
uart19,18,UART,0x20C00006,0x0006,3040,3090,,,星敏B,512,512
|
||||||
|
uart25,25,UART,0x20C00007,0x0007,3046,3096,,,GNSS,512,512
|
||||||
|
can_a,1,CAN,0x20860000,0x0101,20031,10031,,,canA,512,512
|
||||||
|
can_b,2,CAN,0x20860000,0x0101,20032,10032,,,canB,512,512
|
||||||
|
22
config_files/device_config_29260515.csv
Normal file
22
config_files/device_config_29260515.csv
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
名称,编号,协议类型,内存基地址,内存偏移地址,port,remote_port,tcp_local_port,tcp_bus_port,说明,发送缓存区大小,接收缓存区大小
|
||||||
|
光纤陀螺A,1,UART,0x20800000,0x0000,4000,11000,,,Uart0,512,512
|
||||||
|
反作用轮A,2,CAN,0x20810000,0x0100,4001,11011,,,CAN0,1024,1024
|
||||||
|
1553B0,3,1553B,0x20820000,0x0200,4002,11022,,,测控模块,2048,2048
|
||||||
|
电压采集,4,AD,0x20830000,0x0300,4003,11033,,,AD0,512,512
|
||||||
|
开关量输出,1,OC,0x20840000,0x0400,4004,11044,,,OC0,512,512
|
||||||
|
光纤陀螺B,1,UART,0x20850000,0X0001,4005,11055,,,UART1,512,512
|
||||||
|
星敏A,1,CAN,0x20860000,0x0101,4006,11066,,,CAN1,512,512
|
||||||
|
数传A,1,1553B,0x20870000,0x0201,4007,11077,,,1553B1,512,512
|
||||||
|
adc1,1,AD,0x21800000,0x0488,4018,11088,,,正电压采集热敏量采集,512,512
|
||||||
|
继电器输出,2,OC,0x20890000,0x0401,4009,11099,,,OC1,512,512
|
||||||
|
uart24,24,UART,0x20800c80,0x0000,13045,13095,13024,12024,地测串口,1024,2048
|
||||||
|
uart26,26,UART,0x20C00000,0x0000,3145,3195,13026,12026,高速通信,2048,512
|
||||||
|
uart0,0,UART,0x20C00001,0x0001,3021,3071,,,陀螺A,512,512
|
||||||
|
uart1,1,UART,0x20C00002,0x0002,3022,3072,,,陀螺B,512,512
|
||||||
|
uart2,2,UART,0x20C00003,0x0003,3023,3073,,,陀螺C,512,512
|
||||||
|
uart3,3,UART,0x20C00004,0x0004,3024,3074,,,预留,512,512
|
||||||
|
uart18,18,UART,0x20C00005,0x0005,3039,3089,,,星敏C,512,512
|
||||||
|
uart19,18,UART,0x20C00006,0x0006,3040,3090,,,星敏B,512,512
|
||||||
|
uart25,25,UART,0x20C00007,0x0007,3046,3096,,,GNSS,512,512
|
||||||
|
canA,1,CAN,0x20860000,0x0101,20031,10031,,,canA,512,512
|
||||||
|
canB,2,CAN,0x20860000,0x0101,20032,10032,,,canB,512,512
|
||||||
|
BIN
config_files/device_config_old.xlsx
Normal file
BIN
config_files/device_config_old.xlsx
Normal file
Binary file not shown.
BIN
dependencys/colorama-0.4.6-py2.py3-none-any.whl
Normal file
BIN
dependencys/colorama-0.4.6-py2.py3-none-any.whl
Normal file
Binary file not shown.
BIN
dependencys/et_xmlfile-2.0.0-py3-none-any.whl
Normal file
BIN
dependencys/et_xmlfile-2.0.0-py3-none-any.whl
Normal file
Binary file not shown.
BIN
dependencys/loguru-0.7.3-py3-none-any.whl
Normal file
BIN
dependencys/loguru-0.7.3-py3-none-any.whl
Normal file
Binary file not shown.
BIN
dependencys/numpy-2.4.4-cp311-cp311-win_amd64.whl
Normal file
BIN
dependencys/numpy-2.4.4-cp311-cp311-win_amd64.whl
Normal file
Binary file not shown.
BIN
dependencys/openpyxl-3.1.5-py2.py3-none-any.whl
Normal file
BIN
dependencys/openpyxl-3.1.5-py2.py3-none-any.whl
Normal file
Binary file not shown.
BIN
dependencys/pandas-3.0.2-cp311-cp311-win_amd64.whl
Normal file
BIN
dependencys/pandas-3.0.2-cp311-cp311-win_amd64.whl
Normal file
Binary file not shown.
BIN
dependencys/psutil-7.2.2-cp37-abi3-win_amd64.whl
Normal file
BIN
dependencys/psutil-7.2.2-cp37-abi3-win_amd64.whl
Normal file
Binary file not shown.
BIN
dependencys/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
Normal file
BIN
dependencys/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
Normal file
Binary file not shown.
BIN
dependencys/six-1.17.0-py2.py3-none-any.whl
Normal file
BIN
dependencys/six-1.17.0-py2.py3-none-any.whl
Normal file
Binary file not shown.
BIN
dependencys/tzdata-2026.1-py2.py3-none-any.whl
Normal file
BIN
dependencys/tzdata-2026.1-py2.py3-none-any.whl
Normal file
Binary file not shown.
BIN
dependencys/win32_setctime-1.2.0-py3-none-any.whl
Normal file
BIN
dependencys/win32_setctime-1.2.0-py3-none-any.whl
Normal file
Binary file not shown.
236
fault_inject_simulator.log
Normal file
236
fault_inject_simulator.log
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
2026-02-26 22:55:07 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-26 22:55:07 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:8880 | 发送次数:500
|
||||||
|
2026-02-26 22:55:08 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:8880 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-26 22:55:08 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-26 22:55:08 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-26 22:55:08 | INFO | 端口统计:
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:Uart0 | 端口: 8880 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:CAN1 | 端口: 8881 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:1553B0 | 端口: 8882 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:AD0 | 端口: 8883 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:OC0 | 端口: 8884 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:Uart1 | 端口: 8885 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:CAN2 | 端口: 8886 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:1553B1 | 端口: 8887 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:AD1 | 端口: 8888 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | 外设:OC1 | 端口: 8889 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:55:08 | INFO | ====================================================================================================
|
||||||
|
2026-02-26 22:55:08 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-26 22:56:44 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-26 22:56:44 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:4000 | 发送次数:500
|
||||||
|
2026-02-26 22:56:45 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:4000 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-26 22:56:45 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-26 22:56:45 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-26 22:56:45 | INFO | 端口统计:
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:CAN1 | 端口: 8881 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:1553B0 | 端口: 8882 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:AD0 | 端口: 8883 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:OC0 | 端口: 8884 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:Uart1 | 端口: 8885 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:CAN2 | 端口: 8886 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:1553B1 | 端口: 8887 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:AD1 | 端口: 8888 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | 外设:OC1 | 端口: 8889 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-26 22:56:45 | INFO | ====================================================================================================
|
||||||
|
2026-02-26 22:56:45 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-26 23:01:41 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-26 23:01:41 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:4000 | 发送次数:500
|
||||||
|
2026-02-26 23:02:31 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:4000 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-26 23:02:31 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-26 23:02:31 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-26 23:02:31 | INFO | 端口统计:
|
||||||
|
2026-02-26 23:02:31 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-26 23:02:31 | INFO | ====================================================================================================
|
||||||
|
2026-02-26 23:02:31 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-26 23:05:01 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-26 23:05:01 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:4000 | 发送次数:500
|
||||||
|
2026-02-26 23:05:52 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:4000 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-26 23:05:52 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-26 23:05:52 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-26 23:05:52 | INFO | 端口统计:
|
||||||
|
2026-02-26 23:05:52 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-26 23:05:52 | INFO | ====================================================================================================
|
||||||
|
2026-02-26 23:05:52 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-26 23:13:20 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-26 23:13:20 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:4000 | 发送次数:500
|
||||||
|
2026-02-26 23:14:10 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:4000 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-26 23:14:10 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-26 23:14:10 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-26 23:14:10 | INFO | 端口统计:
|
||||||
|
2026-02-26 23:14:10 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-26 23:14:10 | INFO | ====================================================================================================
|
||||||
|
2026-02-26 23:14:10 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-26 23:27:54 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-26 23:27:54 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:4000 | 发送次数:500
|
||||||
|
2026-02-26 23:28:44 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:4000 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-26 23:28:44 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-26 23:28:44 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-26 23:28:44 | INFO | 端口统计:
|
||||||
|
2026-02-26 23:28:44 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-26 23:28:44 | INFO | ====================================================================================================
|
||||||
|
2026-02-26 23:28:44 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:17:43 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:17:43 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:4000 | 发送次数:500
|
||||||
|
2026-02-27 00:18:33 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:4000 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-27 00:18:33 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:18:33 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-27 00:18:33 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:18:33 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:18:33 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:18:33 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:28:00 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:28:00 | INFO | 开始单端口循环发送 | 外设:Uart0 | 端口:4000 | 发送次数:500
|
||||||
|
2026-02-27 00:28:50 | INFO | 单端口发送完成 | 外设:Uart0 | 端口:4000 | 发送统计:{'success': 500, 'failed': 0}
|
||||||
|
2026-02-27 00:28:50 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:28:50 | INFO | 全局统计 | 总发送:500 | 成功:500 | 失败:0
|
||||||
|
2026-02-27 00:28:50 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:28:50 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 500 | 成功: 500 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:28:50 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-27 00:28:50 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:28:50 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:29:57 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:29:57 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN1', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 00:29:57 | WARNING | 外设CAN1不存在,跳过
|
||||||
|
2026-02-27 00:29:57 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 00:29:57 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 00:29:57 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 00:29:59 | INFO | 多端口并发发送完成 | 全局统计:总发送20 | 成功20 | 失败0
|
||||||
|
2026-02-27 00:29:59 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:29:59 | INFO | 全局统计 | 总发送:20 | 成功:20 | 失败:0
|
||||||
|
2026-02-27 00:29:59 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:29:59 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:29:59 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-27 00:29:59 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:29:59 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:30:16 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:30:16 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN1', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 00:30:16 | WARNING | 外设CAN1不存在,跳过
|
||||||
|
2026-02-27 00:30:16 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 00:30:16 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 00:30:16 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 00:30:18 | INFO | 多端口并发发送完成 | 全局统计:总发送20 | 成功20 | 失败0
|
||||||
|
2026-02-27 00:30:18 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:30:18 | INFO | 全局统计 | 总发送:20 | 成功:20 | 失败:0
|
||||||
|
2026-02-27 00:30:18 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:30:18 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:30:18 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 0 | 成功: 0 | 失败: 0 | 成功率:0.00%
|
||||||
|
2026-02-27 00:30:18 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:30:18 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:32:04 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:32:04 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 00:32:04 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 00:32:04 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 00:32:04 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 00:32:06 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 00:32:06 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:32:06 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 00:32:06 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:32:06 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:32:06 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:32:06 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:32:06 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:43:47 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:43:47 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 00:43:47 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 00:43:47 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 00:43:47 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 00:43:49 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 00:43:49 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:43:49 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 00:43:49 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:43:49 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:43:49 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:43:49 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:43:49 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:47:32 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:47:32 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 00:47:32 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 00:47:32 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 00:47:32 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 00:47:34 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 00:47:34 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:47:34 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 00:47:34 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:47:34 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:47:34 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:47:34 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:47:34 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 00:49:20 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 00:49:20 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 00:49:20 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 00:49:20 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 00:49:20 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 00:49:22 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 00:49:22 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 00:49:22 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 00:49:22 | INFO | 端口统计:
|
||||||
|
2026-02-27 00:49:22 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:49:22 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 00:49:22 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 00:49:22 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 09:52:45 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 09:52:45 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 09:52:45 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 09:52:45 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 09:52:45 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 09:52:47 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 09:52:47 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 09:52:47 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 09:52:47 | INFO | 端口统计:
|
||||||
|
2026-02-27 09:52:47 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 09:52:47 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 09:52:47 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 09:52:47 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 09:53:07 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 09:53:07 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 09:53:07 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 09:53:07 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 09:53:07 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 09:53:09 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 09:53:09 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 09:53:09 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 09:53:09 | INFO | 端口统计:
|
||||||
|
2026-02-27 09:53:09 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 09:53:09 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 09:53:09 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 09:53:09 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 14:25:34 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 14:25:34 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 14:25:34 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 14:25:34 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 14:25:34 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 14:25:36 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 14:25:36 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 14:25:36 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 14:25:36 | INFO | 端口统计:
|
||||||
|
2026-02-27 14:25:36 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 14:25:36 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 14:25:36 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 14:25:36 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 22:07:12 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 22:07:12 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 22:07:12 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 22:07:12 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 22:07:12 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 22:07:14 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 22:07:14 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 22:07:14 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 22:07:14 | INFO | 端口统计:
|
||||||
|
2026-02-27 22:07:14 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 22:07:14 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 22:07:14 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 22:07:14 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
|
2026-02-27 22:10:52 | INFO | 故障注入平台UDP模拟器初始化完成
|
||||||
|
2026-02-27 22:10:52 | INFO | 开始多端口并发发送 | 外设列表:['Uart0', 'CAN0', '1553B0', 'AD0', 'OC0'] | 每端口发送次数:20
|
||||||
|
2026-02-27 22:10:52 | WARNING | 外设1553B0不存在,跳过
|
||||||
|
2026-02-27 22:10:52 | WARNING | 外设AD0不存在,跳过
|
||||||
|
2026-02-27 22:10:52 | WARNING | 外设OC0不存在,跳过
|
||||||
|
2026-02-27 22:10:54 | INFO | 多端口并发发送完成 | 全局统计:总发送40 | 成功40 | 失败0
|
||||||
|
2026-02-27 22:10:54 | INFO | ================================================== 发送统计汇总 ==================================================
|
||||||
|
2026-02-27 22:10:54 | INFO | 全局统计 | 总发送:40 | 成功:40 | 失败:0
|
||||||
|
2026-02-27 22:10:54 | INFO | 端口统计:
|
||||||
|
2026-02-27 22:10:54 | INFO | 外设:Uart0 | 端口: 4000 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 22:10:54 | INFO | 外设:CAN0 | 端口: 4001 | 发送: 20 | 成功: 20 | 失败: 0 | 成功率:100.00%
|
||||||
|
2026-02-27 22:10:54 | INFO | ====================================================================================================
|
||||||
|
2026-02-27 22:10:54 | INFO | 故障注入平台UDP模拟器已关闭,资源释放完成
|
||||||
191
main-20260227-01.py
Normal file
191
main-20260227-01.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
import time
|
||||||
|
|
||||||
|
# 设置标准输出和标准错误的编码为UTF-8
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
from midware.ui.main_ui import start_main_ui
|
||||||
|
from midware.config.base_config import init_base_config
|
||||||
|
#from midware.core.data_forward import init_forward_service
|
||||||
|
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 udp_server
|
||||||
|
from midware.network.protocol_dispatcher import dispatch_udp_data
|
||||||
|
|
||||||
|
import midware.config.base_config as base_config
|
||||||
|
|
||||||
|
# from midware.config.base_config import init_base_config
|
||||||
|
# from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
# 先导入配置核心模块
|
||||||
|
from midware.config.base_config import init_base_config, update_device_config, get_device_config
|
||||||
|
# 后导入UDP模块(此时udp_server还是None)
|
||||||
|
from midware.network.udp_handler import MultiUDPServer, udp_server
|
||||||
|
|
||||||
|
# 添加工程根目录到环境变量
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
'''
|
||||||
|
1. 程序入口(main.py)
|
||||||
|
负责初始化日志、加载配置、启动 UI 和核心服务,实现程序一键启动
|
||||||
|
'''
|
||||||
|
# 初始化日志
|
||||||
|
def init_logger():
|
||||||
|
logger.add(
|
||||||
|
"logs/run_{time:YYYY-MM-DD}.log",
|
||||||
|
rotation="daily",
|
||||||
|
level="INFO",
|
||||||
|
format="{time} | {level} | {module}:{line} | {message}"
|
||||||
|
)
|
||||||
|
logger.add(
|
||||||
|
"logs/error_{time:YYYY-MM-DD}.log",
|
||||||
|
rotation="daily",
|
||||||
|
level="ERROR",
|
||||||
|
format="{time} | {level} | {module}:{line} | {message}"
|
||||||
|
)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 初始化日志
|
||||||
|
log = init_logger()
|
||||||
|
log.info("虚拟仿真平台中间层软件启动")
|
||||||
|
logger.info("========== 虚拟仿真平台中间层软件 启动 ==========")
|
||||||
|
##试一下2026/2/27
|
||||||
|
csv_path = "config_files/device_config.csv"
|
||||||
|
|
||||||
|
DEVICE_CONFIG_DICT = load_csv_config(csv_path)
|
||||||
|
|
||||||
|
#try:
|
||||||
|
# 初始化基础配置(外设、UDP、总线)
|
||||||
|
# 步骤1:初始化外设配置(核心!先初始化配置,再实例化其他模块)
|
||||||
|
init_base_config()
|
||||||
|
|
||||||
|
device_config = get_device_config()
|
||||||
|
if not device_config:
|
||||||
|
logger.error("配置加载为空,工程无法启动,退出!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 初始化数据转发核心服务
|
||||||
|
#init_forward_service()
|
||||||
|
|
||||||
|
if not base_config.DEVICE_CONFIG_DICT:
|
||||||
|
logger.error("外设配置加载失败,工程启动终止")
|
||||||
|
else:
|
||||||
|
logger.info("外设配置加载成功")
|
||||||
|
print("程序运行到这里了")
|
||||||
|
|
||||||
|
# 步骤2:实例化UDP单例服务(此时配置已初始化,能读取到有效数据)
|
||||||
|
global udp_server
|
||||||
|
udp_server = MultiUDPServer()
|
||||||
|
logger.info(f"工程启动成功|外设数量:{len(device_config)}|UDP监听端口:{list(udp_server.udp_sockets.keys())}")
|
||||||
|
|
||||||
|
# 初始化UDP服务器
|
||||||
|
#udp_server.__init__() # 确保先初始化
|
||||||
|
|
||||||
|
'''
|
||||||
|
# # 初始化UDP服务器
|
||||||
|
# udp_server.init() # 确保先初始化
|
||||||
|
# 启动主UI
|
||||||
|
start_main_ui()
|
||||||
|
log.info("软件运行中...")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"程序启动失败:{str(e)}", exc_info=True)
|
||||||
|
sys.exit(1)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# 步骤3:工程主循环(阻塞运行,处理UDP数据)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# 非阻塞接收所有UDP端口数据
|
||||||
|
recv_data = udp_server.recv_multi_udp_hex()
|
||||||
|
if recv_data:
|
||||||
|
# 后续对接协议解析层/总线适配层的逻辑写在这里
|
||||||
|
for data in recv_data:
|
||||||
|
port = data["port"]
|
||||||
|
dev_name = data["dev_name"]
|
||||||
|
protocol = data["protocol"]
|
||||||
|
raw_data = data["raw_data"]
|
||||||
|
if len(raw_data) > 0:
|
||||||
|
#打印原始数据(测试用)
|
||||||
|
print(f"接收到 {dev_name} 的原始数据:{raw_data}, 协议类型:{protocol}")
|
||||||
|
|
||||||
|
# 根据协议类型进行数据解析和转发
|
||||||
|
print("后续根据设备的协议类型进行数据解析和转发...")
|
||||||
|
|
||||||
|
#判断协议类型,采用不同解析和转发方法
|
||||||
|
if protocol == "UART":
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 示例:直接回传数据到故障注入软件(测试用)
|
||||||
|
# 2. 分发处理数据(核心调用)
|
||||||
|
dispatch_udp_data(recv_data)
|
||||||
|
|
||||||
|
# 示例:直接回传数据到故障注入软件(测试用)
|
||||||
|
udp_server.send_to_fault(raw_data, dev_name)
|
||||||
|
# 主循环间隔1us,不影响实时性
|
||||||
|
time.sleep(1e-6)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("用户终止工程,开始释放资源...")
|
||||||
|
finally:
|
||||||
|
# 退出时关闭UDP服务
|
||||||
|
udp_server.close()
|
||||||
|
logger.info("========== 虚拟仿真平台中间层软件 退出 ==========")
|
||||||
|
|
||||||
|
|
||||||
|
# if name == "main":
|
||||||
|
#test_config = load_excel_config("../../config_files/device_config.xlsx")
|
||||||
|
#test_config = load_excel_config("config_files/device_config.xlsx")
|
||||||
|
|
||||||
|
# test_config = load_csv_config("config_files/device_config.csv") ##暂时注释这行
|
||||||
|
# print(test_config)
|
||||||
|
'''
|
||||||
|
#测试校验和代码(单独运行该文件时执行)
|
||||||
|
|
||||||
|
#测试 UART 校验:原始数据 + b'x9c\x47'(crc16_modbus 校验位)
|
||||||
|
uart_data = b"test_uart_data" + b'\x9c\x47'
|
||||||
|
print (f"UART 校验结果:{check_checksum (uart_data, 'UART', 'Uart0')}")
|
||||||
|
#测试 CAN 校验:原始数据(10 字节)+b'\x1a\x2b'(crc16_ccitt 校验位),总长度 12≤13(11+2)
|
||||||
|
can_data = b"test_can_123" + b'\x1a\x2b'
|
||||||
|
print (f"CAN 校验结果:{check_checksum (can_data, 'CAN', 'CAN1')}")
|
||||||
|
print (f"CAN 帧完整性结果:{verify_frame_integrity (can_data, 'CAN', 'CAN1')}")
|
||||||
|
#测试 AD 校验:原始数据 + b'\x5f'(累加和校验位)
|
||||||
|
ad_data = b"test_ad" + b'\x5f'
|
||||||
|
print (f"AD 校验结果:{check_checksum (ad_data, 'AD', 'AD0')}")
|
||||||
|
#测试失败场景:校验位错误
|
||||||
|
wrong_checksum_data = b"test_uart_data" + b'\x00\x00'
|
||||||
|
print (f"UART 校验失败场景结果:{check_checksum (wrong_checksum_data, 'UART', 'Uart0')}")
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 初始化基础配置
|
||||||
|
init_base_config()
|
||||||
|
|
||||||
|
# 初始化UDP服务器
|
||||||
|
udp_server.init() # 确保先初始化
|
||||||
|
|
||||||
|
# 测试数据接收
|
||||||
|
data_list = udp_server.recv_multi_udp_hex()
|
||||||
|
for data in data_list:
|
||||||
|
dev_name = data["dev_name"]
|
||||||
|
raw_data = data["raw_data"]
|
||||||
|
# 对每个外设的原始数据进行协议处理...
|
||||||
|
|
||||||
|
# 2. 向故障注入软件发送数据
|
||||||
|
if len(raw_data) > 0:
|
||||||
|
udp_server.send_to_fault(raw_data, dev_name="Uart0")
|
||||||
|
|
||||||
|
# 3. 外设配置更新后,重新加载UDP端口
|
||||||
|
udp_server.reload_sockets()
|
||||||
|
|
||||||
|
# 4. 程序退出时关闭所有套接字
|
||||||
|
udp_server.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"程序启动失败:{str(e)}", exc_info=True)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
199
main-20260228.py
Normal file
199
main-20260228.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# -*- 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)
|
||||||
171
main.py
Normal file
171
main.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
"""
|
||||||
|
anthor:Tan Mingyan
|
||||||
|
time:2026/3/6
|
||||||
|
功能:主函数,实现多线程,UDP收发和总线收发
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# main.py 导入解耦模块
|
||||||
|
from midware.core import start_all_threads, stop_all_threads
|
||||||
|
from midware.network.udp_handler import MultiUDPServer
|
||||||
|
from loguru import logger
|
||||||
|
from midware.config.base_config import (
|
||||||
|
init_base_config,
|
||||||
|
update_device_config,
|
||||||
|
get_device_config
|
||||||
|
)
|
||||||
|
import midware.config.base_config as base_config
|
||||||
|
from midware.drivers import DRIVER_MAPPING #2026/3/9
|
||||||
|
|
||||||
|
# 添加工程根目录到环境变量(确保配置文件路径正确)
|
||||||
|
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")
|
||||||
|
|
||||||
|
# 驱动配置(可从yaml文件加载)波特率暂时无效
|
||||||
|
DRIVER_CONFIGS = {
|
||||||
|
"1553B": {"board_id": 0, "bc_address": 0x01},
|
||||||
|
"CAN": {"channel": 0, "baudrate": 500000},
|
||||||
|
"UART": {"port": "COM3", "baudrate": 9600},
|
||||||
|
"AD": {"sampling_rate": 1000},
|
||||||
|
"OC": {"output_voltage": 5.0},
|
||||||
|
"网络": {"ip": "192.168.1.100", "port": 8080}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 修复点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
|
||||||
|
|
||||||
|
# 启动线程(无需写冗长路径)
|
||||||
|
#threads = start_all_threads(udp_server)
|
||||||
|
# main.py 核心修改
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 初始化日志、加载配置、实例化UDP服务(原有逻辑)...
|
||||||
|
# 初始化日志(最先执行,避免后续报错无日志)
|
||||||
|
log = init_logger()
|
||||||
|
log.info("虚拟仿真平台中间层软件启动")
|
||||||
|
logger.info("========== 虚拟仿真平台中间层软件 启动 ==========")
|
||||||
|
|
||||||
|
# 初始化基础配置
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 启动所有解耦线程(替代原主循环)
|
||||||
|
#threads = start_all_threads(udp_server)
|
||||||
|
threads = start_all_threads(
|
||||||
|
udp_server=udp_server,
|
||||||
|
bus_driver_configs=DRIVER_CONFIGS # 2026/3/9 新增驱动配置参数
|
||||||
|
)
|
||||||
|
# main.py 主线程
|
||||||
|
'''
|
||||||
|
while True:
|
||||||
|
# 打印各队列长度
|
||||||
|
logger.info("队列状态:")
|
||||||
|
for proto, q in bus_recv_queues.items():
|
||||||
|
logger.info(f" {proto} 接收队列:{q.qsize()}/{q.maxsize}")
|
||||||
|
for proto, q in bus_send_queues.items():
|
||||||
|
logger.info(f" {proto} 发送队列:{q.qsize()}/{q.maxsize}")
|
||||||
|
time.sleep(1) # 每秒监控一次
|
||||||
|
'''
|
||||||
|
# 主线程:仅负责等待退出信号,不处理业务
|
||||||
|
|
||||||
|
# main.py
|
||||||
|
from midware.drivers.renode_agent import renode, renode_async_data_dispatcher
|
||||||
|
|
||||||
|
# 只加这一行
|
||||||
|
if not renode.start():
|
||||||
|
exit()
|
||||||
|
time.sleep(5)
|
||||||
|
renode.start_async_listener(renode_async_data_dispatcher)
|
||||||
|
time.sleep(10)
|
||||||
|
# 加载完DEVICE_CONFIG_DICT之后加:
|
||||||
|
|
||||||
|
from midware.drivers.renode_agent import start_renode_uart_redirect
|
||||||
|
from midware.drivers.tcp_uart_agent import start_all_tcp_uart_clients
|
||||||
|
|
||||||
|
# 启动Renode的UART TCP重定向
|
||||||
|
start_renode_uart_redirect()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 启动所有TCP客户端接收
|
||||||
|
start_all_tcp_uart_clients()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1) # 主线程休眠,避免CPU占用
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("用户终止程序,开始优雅退出...")
|
||||||
|
stop_all_threads() # 等待队列处理完成
|
||||||
|
udp_server.close() # 关闭UDP服务
|
||||||
|
udp_server.socket.close() # 强制关闭系统套接字
|
||||||
|
logger.info("程序正常退出")
|
||||||
|
|
||||||
|
|
||||||
0
midware/bus/__init__.py
Normal file
0
midware/bus/__init__.py
Normal file
55
midware/bus/cache_handler.py
Normal file
55
midware/bus/cache_handler.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
'''
|
||||||
|
5. 缓存交互模块(midware/bus/cache_handler.py)
|
||||||
|
实现与 SJA1000/61580 共享缓存的交互,提供WriteDoubleWord/ReadDoubleWord核心操作,对接虚拟芯片:
|
||||||
|
'''
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
class CacheHandler:
|
||||||
|
"""缓存交互层,实现sysbus WriteDoubleWord/ReadDoubleWord操作"""
|
||||||
|
def __init__(self):
|
||||||
|
# 模拟共享缓存(字典实现,key=内存地址,value=32位无符号整数)
|
||||||
|
self.shared_cache = {}
|
||||||
|
logger.info("共享缓存初始化完成(模拟SJA1000/61580)")
|
||||||
|
|
||||||
|
def write_double_word(self, mem_addr, data):
|
||||||
|
"""
|
||||||
|
写入双字(32位)数据到共享缓存
|
||||||
|
:param mem_addr: 内存地址(int)
|
||||||
|
:param data: 16/32位数据(int),自动转为32位无符号整数
|
||||||
|
"""
|
||||||
|
if not isinstance(mem_addr, int) or not isinstance(data, int):
|
||||||
|
logger.error("写入缓存参数错误,地址/数据必须为整数")
|
||||||
|
return False
|
||||||
|
# 转为32位无符号整数
|
||||||
|
|
||||||
|
##给RENODE的写入的语句未定
|
||||||
|
self.shared_cache[mem_addr] = data & 0xFFFFFFFF
|
||||||
|
logger.debug(f"写入缓存:地址{hex(mem_addr)},数据{hex(self.shared_cache[mem_addr])}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def read_double_word(self, mem_addr):
|
||||||
|
"""
|
||||||
|
从共享缓存读取双字(32位)数据
|
||||||
|
:param mem_addr: 内存地址(int)
|
||||||
|
:return: 32位无符号整数,地址不存在返回0
|
||||||
|
"""
|
||||||
|
if not isinstance(mem_addr, int):
|
||||||
|
logger.error("读取缓存参数错误,地址必须为整数")
|
||||||
|
return 0
|
||||||
|
data = self.shared_cache.get(mem_addr, 0)
|
||||||
|
logger.debug(f"读取缓存:地址{hex(mem_addr)},数据{hex(data)}")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def clear_cache(self, mem_addr=None):
|
||||||
|
"""清空指定地址/全部缓存"""
|
||||||
|
if mem_addr:
|
||||||
|
if mem_addr in self.shared_cache:
|
||||||
|
del self.shared_cache[mem_addr]
|
||||||
|
logger.debug(f"清空缓存地址:{hex(mem_addr)}")
|
||||||
|
else:
|
||||||
|
self.shared_cache.clear()
|
||||||
|
logger.info("清空全部共享缓存")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 单例模式,全局唯一缓存实例
|
||||||
|
cache_handler = CacheHandler()
|
||||||
0
midware/bus/chip_register.py
Normal file
0
midware/bus/chip_register.py
Normal file
0
midware/config/__init__.py
Normal file
0
midware/config/__init__.py
Normal file
BIN
midware/config/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/config/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/config/__pycache__/base_config.cpython-311.pyc
Normal file
BIN
midware/config/__pycache__/base_config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/config/__pycache__/excel_config.cpython-311.pyc
Normal file
BIN
midware/config/__pycache__/excel_config.cpython-311.pyc
Normal file
Binary file not shown.
179
midware/config/base_config.py
Normal file
179
midware/config/base_config.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import os
|
||||||
|
from loguru import logger
|
||||||
|
import threading
|
||||||
|
from midware.config.excel_config import load_csv_config
|
||||||
|
|
||||||
|
'''
|
||||||
|
3. 基础配置模块(midware/config/base_config.py)
|
||||||
|
定义全局配置常量,初始化外设、UDP、总线基础参数,预留配置动态加载接口:
|
||||||
|
'''
|
||||||
|
# 基础配置 - UDP(故障注入软件交互)
|
||||||
|
UDP_CONFIG = {
|
||||||
|
"LOCAL_IP": "10.20.48.138", # 本地UDP地址,用于回环测试,自测时候是"127.0.0.1",科学所是"192.168.0.150"","10.20.48.138"
|
||||||
|
"LOCAL_PORT": 8888, # 本地UDP端口
|
||||||
|
"FAULT_INJECT_IP": "10.20.48.129",# 故障注入软件IP,用于回环测试,自测时候是"127.0.0.1",科学所是"192.168.0.130"","10.20.48.129"
|
||||||
|
"FAULT_INJECT_PORT": 18889, # 故障注入软件端口
|
||||||
|
"BUFFER_SIZE": 4096 # UDP缓冲区大小
|
||||||
|
}
|
||||||
|
|
||||||
|
# 基础配置 - 外设总线(支持UART/CAN/1553B/AD/OC)
|
||||||
|
BUS_CONFIG = {
|
||||||
|
"SUPPORT_BUS": ["UART", "CAN", "1553B", "AD", "OC"],
|
||||||
|
"MAX_DEVICE_NUM": 10, # 最大支持10个单机
|
||||||
|
"WRITE_BYTE_UART": 2, # UART每次写入2字节
|
||||||
|
"WRITE_BYTE_CAN": 2, # CAN每次写入2字节
|
||||||
|
"READ_BYTE_CAN": 1, # CAN每次读取1字节
|
||||||
|
"WRITE_BYTE_1553B": 2, # 1553B每次写入2字节
|
||||||
|
"WRITE_BYTE_AD": 2, # AD每次写入2字节
|
||||||
|
"READ_BYTE_OC": 1 # OC每次读取1字节
|
||||||
|
}
|
||||||
|
|
||||||
|
# 基础配置 - UART芯片寄存器(初始写死,后续由chip_config.ini加载)
|
||||||
|
UART_CHIP_CONFIG = {
|
||||||
|
"TBR": 0x00, # 发送FIFO
|
||||||
|
"FIFO_STATUS": 0x04, # FIFO状态寄存器
|
||||||
|
"FRAME_COUNT": 0x08, # 帧计数
|
||||||
|
"FIFO_REMAIN": 0x0C, # FIFO剩余字节数
|
||||||
|
"CLOCK_CONFIG": 0x10, # 时钟配置
|
||||||
|
"SCRAMBLE": 0x14, # 加解扰使能
|
||||||
|
"RESET": 0x7C # 复位
|
||||||
|
}
|
||||||
|
|
||||||
|
# 全局外设配置字典,存储已启用的外设信息
|
||||||
|
# ==================== 全局配置(唯一数据源,仅在此文件定义)====================
|
||||||
|
# 初始化为空字典,由init_base_config()初始化
|
||||||
|
DEVICE_CONFIG_DICT = {} #{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {}}
|
||||||
|
|
||||||
|
# 配置读写锁(保证多线程安全,防止读写冲突)
|
||||||
|
CONFIG_LOCK = threading.RLock()
|
||||||
|
|
||||||
|
# ==================== 日志初始化(工程全局)====================
|
||||||
|
def init_logger():
|
||||||
|
os.makedirs("logs", exist_ok=True)
|
||||||
|
# 运行日志+错误日志分离
|
||||||
|
logger.add("logs/run_{time:YYYYMMDDHHmmss}.log", level="INFO",
|
||||||
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}")
|
||||||
|
logger.add("logs/error_{time:YYYYMMDDHHmmss}.log", level="ERROR",
|
||||||
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {file}:{line} | {message}")
|
||||||
|
# 工程启动时自动初始化日志
|
||||||
|
init_logger()
|
||||||
|
|
||||||
|
'''
|
||||||
|
# 加载xlsx文件
|
||||||
|
def init_base_config():
|
||||||
|
"""初始化基础配置,加载默认外设配置"""
|
||||||
|
try:
|
||||||
|
# 加载默认Excel配置(后续实现)
|
||||||
|
from midware.config.excel_config import load_excel_config
|
||||||
|
default_excel = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "config_files/device_config.xlsx")
|
||||||
|
if os.path.exists(default_excel):
|
||||||
|
DEVICE_CONFIG_DICT = load_excel_config(default_excel)
|
||||||
|
logger.info(f"加载默认Excel配置,共{len(DEVICE_CONFIG_DICT)}个外设")
|
||||||
|
else:
|
||||||
|
logger.warning(f"默认配置文件不存在:{default_excel},使用空配置")
|
||||||
|
logger.info("基础配置初始化完成")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"基础配置初始化失败:{str(e)}", exc_info=True)
|
||||||
|
raise e
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
# midware/config/base_config.py 中init_base_config函数
|
||||||
|
def init_base_config():
|
||||||
|
"""初始化基础配置,加载默认外设配置"""
|
||||||
|
global DEVICE_CONFIG_DICT
|
||||||
|
try:
|
||||||
|
# 替换为CSV加载方法
|
||||||
|
#from midware.config.excel_config import load_csv_config
|
||||||
|
default_csv = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "config_files/device_config.csv")
|
||||||
|
if os.path.exists(default_csv):
|
||||||
|
#global DEVICE_CONFIG_DICT
|
||||||
|
DEVICE_CONFIG_DICT = load_csv_config(default_csv)
|
||||||
|
logger.info(f"加载默认CSV配置,共{len(DEVICE_CONFIG_DICT)}个外设")
|
||||||
|
else:
|
||||||
|
logger.warning(f"默认配置文件不存在:{default_csv},使用空配置")
|
||||||
|
DEVICE_CONFIG_DICT = {}
|
||||||
|
logger.info("基础配置初始化完成")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"基础配置初始化失败:{str(e)}", exc_info=True)
|
||||||
|
DEVICE_CONFIG_DICT = {}
|
||||||
|
#raise e #注释掉这行,避免程序崩溃
|
||||||
|
|
||||||
|
def update_device_config(new_config: dict):
|
||||||
|
"""
|
||||||
|
对外提供配置更新方法(如UI勾选外设、导入新配置时调用)
|
||||||
|
:param new_config: 新的外设配置字典,格式与DEVICE_CONFIG_DICT一致
|
||||||
|
"""
|
||||||
|
global DEVICE_CONFIG_DICT
|
||||||
|
if isinstance(new_config, dict):
|
||||||
|
DEVICE_CONFIG_DICT = new_config
|
||||||
|
logger.info(f"外设配置已更新,当前共{len(DEVICE_CONFIG_DICT)}个外设")
|
||||||
|
# 配置更新后,自动触发UDP套接字重新加载(关键:保证UDP端口与配置同步)
|
||||||
|
from midware.network.udp_handler import udp_server
|
||||||
|
udp_server.reload_sockets()
|
||||||
|
else:
|
||||||
|
logger.error("配置更新失败:新配置非字典类型")
|
||||||
|
# 工程启动时自动初始化日志
|
||||||
|
#init_logger()
|
||||||
|
'''
|
||||||
|
|
||||||
|
# ==================== 配置核心方法(对外暴露,唯一入口)====================
|
||||||
|
def init_base_config(csv_path: str = None):
|
||||||
|
"""
|
||||||
|
初始化外设配置:工程启动时**唯一调用**,加载CSV配置到DEVICE_CONFIG_DICT
|
||||||
|
:param csv_path: 配置文件路径,默认取工程根目录config_files/device_config.csv
|
||||||
|
"""
|
||||||
|
global DEVICE_CONFIG_DICT # 声明修改全局变量
|
||||||
|
if csv_path is None:
|
||||||
|
# 自动拼接工程根目录的配置文件路径
|
||||||
|
csv_path = os.path.join(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
||||||
|
"config_files/device_config.csv"
|
||||||
|
)
|
||||||
|
# 加锁保证初始化原子性,防止多线程同时修改
|
||||||
|
with CONFIG_LOCK:
|
||||||
|
try:
|
||||||
|
if os.path.exists(csv_path):
|
||||||
|
# 加载CSV配置并覆盖全局变量
|
||||||
|
DEVICE_CONFIG_DICT = load_csv_config(csv_path)
|
||||||
|
logger.info(f"配置初始化成功|加载{len(DEVICE_CONFIG_DICT)}个外设|配置文件:{csv_path}")
|
||||||
|
else:
|
||||||
|
DEVICE_CONFIG_DICT = {}
|
||||||
|
logger.error(f"配置初始化失败|配置文件不存在:{csv_path}")
|
||||||
|
except Exception as e:
|
||||||
|
DEVICE_CONFIG_DICT = {}
|
||||||
|
logger.error(f"配置初始化异常|{str(e)}", exc_info=True)
|
||||||
|
return DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
def update_device_config(new_config: dict):
|
||||||
|
"""
|
||||||
|
唯一配置更新入口:修改配置后自动同步UDP端口
|
||||||
|
【关键】延迟导入udp_server,避免模块顶层循环导入
|
||||||
|
"""
|
||||||
|
global DEVICE_CONFIG_DICT
|
||||||
|
with CONFIG_LOCK:
|
||||||
|
if not isinstance(new_config, dict):
|
||||||
|
logger.error("配置更新失败|新配置非字典类型")
|
||||||
|
return False
|
||||||
|
# 覆盖全局配置
|
||||||
|
DEVICE_CONFIG_DICT = new_config.copy() # 深拷贝,防止外部修改源字典
|
||||||
|
logger.info(f"配置更新成功|当前外设数量:{len(DEVICE_CONFIG_DICT)}")
|
||||||
|
# 延迟导入:仅在函数内导入,避免模块加载时循环依赖
|
||||||
|
try:
|
||||||
|
from midware.network.udp_handler import udp_server
|
||||||
|
udp_server.reload_sockets(new_config) # 传参同步配置,不依赖全局引用
|
||||||
|
except ImportError as e:
|
||||||
|
logger.warning(f"UDP配置同步失败|未找到UDP模块:{str(e)}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_device_config():
|
||||||
|
"""
|
||||||
|
全局配置**只读接口**:所有其他模块必须通过此方法获取配置
|
||||||
|
返回副本,防止外部修改原全局变量导致数据混乱
|
||||||
|
"""
|
||||||
|
with CONFIG_LOCK:
|
||||||
|
return DEVICE_CONFIG_DICT.copy() # 返回深拷贝,彻底隔离原变量
|
||||||
|
|
||||||
|
def get_udp_config():
|
||||||
|
"""UDP基础配置只读接口"""
|
||||||
|
return UDP_CONFIG.copy()
|
||||||
11
midware/config/device_config.csv
Normal file
11
midware/config/device_config.csv
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
名称,协议类型,内存基地址,内存偏移地址,port,说明,发送缓存区大小,接收缓存区大小
|
||||||
|
光纤陀螺A,UART,0x20800000,0x0000,4000,Uart0,512,512
|
||||||
|
反作用轮A,CAN,0x20810000,0x0100,4001,CAN0,1024,1024
|
||||||
|
1553B0,1553B,0x20820000,0x0200,4002,测控模块,2048,2048
|
||||||
|
电压采集,AD,0x20830000,0x0300,4003,AD0,512,512
|
||||||
|
开关量输出,OC,0x20840000,0x0400,4004,OC0,512,512
|
||||||
|
光纤陀螺B,UART,0x20850000,0X0001,4005,UART1,512,512
|
||||||
|
星敏A,CAN,0x20860000,0x0101,4006,CAN1,512,512
|
||||||
|
数传A,1553B,0x20870000,0x0201,4007,1553B1,512,512
|
||||||
|
电流采集,AD,0x20880000,0x0301,4018,AD1,512,512
|
||||||
|
继电器输出,OC,0x20890000,0x0401,4009,OC1,512,512
|
||||||
|
135
midware/config/excel_config-20260226-02.py
Normal file
135
midware/config/excel_config-20260226-02.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Excel 配置解析(excel_config.py):基于openpyxl实现 Excel 文件解析,提取外设名称、UDP 端口、协议类型、基地址等信息,存入DEVICE_CONFIG_DICT。
|
||||||
|
'''
|
||||||
|
import openpyxl
|
||||||
|
from loguru import logger
|
||||||
|
import re
|
||||||
|
import pandas as pd
|
||||||
|
import os # 新增:用于删除临时文件
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
# 设置标准输出和标准错误的编码为UTF-8
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
# 添加工程根目录到环境变量
|
||||||
|
# sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
def hex_to_int (hex_str):
|
||||||
|
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x2080,0000、0x0000 1234)
|
||||||
|
:param hex_str: 十六进制字符串
|
||||||
|
:return: 转换后的整数,失败返回 0
|
||||||
|
"""
|
||||||
|
if not isinstance (hex_str, str):
|
||||||
|
return 0 # 非字符串直接返回0
|
||||||
|
# 移除逗号、空格等非十六进制字符
|
||||||
|
clean_hex = re.sub (r'[,\s]', '', hex_str.strip ())
|
||||||
|
try:
|
||||||
|
return int (clean_hex, 16) if clean_hex else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
logger.warning (f"十六进制转换失败:{hex_str},默认返回 0")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def load_csv_config (file_path):
|
||||||
|
"""加载 Excel/CSV 外设配置文件,解析核心配置项
|
||||||
|
:param file_path: Excel/CSV文件路径(xlsx/xls/csv)
|
||||||
|
:return: 外设配置字典,key = 外设名称,value = 配置项字典
|
||||||
|
"""
|
||||||
|
device_config = {}
|
||||||
|
wb = None # 初始化工作簿对象
|
||||||
|
temp_excel = "file.xlsx" # CSV转换的临时Excel文件
|
||||||
|
try:
|
||||||
|
# 处理CSV文件:转换为Excel后解析
|
||||||
|
if file_path.endswith('.csv'):
|
||||||
|
df = pd.read_csv(file_path)
|
||||||
|
df.to_excel(temp_excel, index=False)
|
||||||
|
file_path = temp_excel # 切换为临时Excel文件路径
|
||||||
|
|
||||||
|
# 打开工作簿(兼容直接传入Excel文件的情况)
|
||||||
|
wb = openpyxl.load_workbook(file_path, data_only=True)
|
||||||
|
ws = wb.active
|
||||||
|
logger.info(f"成功打开配置文件:{file_path},工作表:{ws.title}")
|
||||||
|
|
||||||
|
# 获取表头行,匹配核心配置列(兼容列名大小写 / 空格)
|
||||||
|
header = [cell.value.strip() if cell.value else "" for cell in ws[1]]
|
||||||
|
col_mapping = {
|
||||||
|
"名称": header.index([h for h in header if h.lower() == "名称"][0]) if [h for h in header if h.lower() == "名称"] else -1,
|
||||||
|
"协议类型": header.index([h for h in header if h.lower() in ["协议", "协议类型"]][0]) if [h for h in header if h.lower() in ["协议", "协议类型"]] else -1,
|
||||||
|
"内存基地址": header.index([h for h in header if h.lower() in ["内存基地址", "基地址"]][0]) if [h for h in header if h.lower() in ["内存基地址", "基地址"]] else -1,
|
||||||
|
"内存偏移地址": header.index([h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]][0]) if [h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]] else -1,
|
||||||
|
"UDP端口": header.index([h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]][0]) if [h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]] else -1,
|
||||||
|
"说明": header.index([h for h in header if h.lower() == "说明"][0]) if [h for h in header if h.lower() == "说明"] else -1,
|
||||||
|
"发送缓存区大小": header.index([h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]][0]) if [h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]] else -1,
|
||||||
|
"接收缓存区大小": header.index([h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]][0]) if [h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]] else -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 校验核心列(名称、协议、基地址必须存在)
|
||||||
|
required_cols = ["名称", "协议类型", "内存基地址"]
|
||||||
|
for col in required_cols:
|
||||||
|
if col_mapping[col] == -1:
|
||||||
|
logger.error(f"配置文件缺少核心列:{col},请检查表头")
|
||||||
|
return device_config
|
||||||
|
|
||||||
|
# 遍历数据行(从第 2 行开始)
|
||||||
|
for row_num, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
|
||||||
|
if not row[col_mapping["名称"]]: # 外设名称为空则跳过
|
||||||
|
logger.warning(f"第 {row_num} 行:外设名称为空,跳过该行")
|
||||||
|
continue
|
||||||
|
|
||||||
|
dev_name = row[col_mapping["名称"]].strip()
|
||||||
|
# 解析单行配置,缺省项赋默认值
|
||||||
|
dev_info = {
|
||||||
|
"protocol": row[col_mapping["协议类型"]].strip().upper() if row[col_mapping["协议类型"]] else "",
|
||||||
|
"base_addr": hex_to_int(row[col_mapping["内存基地址"]]),
|
||||||
|
"offset_addr": hex_to_int(row[col_mapping["内存偏移地址"]]),
|
||||||
|
"udp_port": int(row[col_mapping["UDP端口"]]) if (row[col_mapping["UDP端口"]] and str(row[col_mapping["UDP端口"]]).isdigit()) else 8888,
|
||||||
|
"desc": row[col_mapping["说明"]].strip() if row[col_mapping["说明"]] else "无",
|
||||||
|
"send_cache": int(row[col_mapping["发送缓存区大小"]]) if (row[col_mapping["发送缓存区大小"]] and str(row[col_mapping["发送缓存区大小"]]).isdigit()) else 512,
|
||||||
|
"recv_cache": int(row[col_mapping["接收缓存区大小"]]) if (row[col_mapping["接收缓存区大小"]] and str(row[col_mapping["接收缓存区大小"]]).isdigit()) else 512,
|
||||||
|
"enable": True # 默认启用该外设
|
||||||
|
}
|
||||||
|
|
||||||
|
# 校验协议类型合法性
|
||||||
|
if dev_info["protocol"] not in ["UART", "CAN", "1553B", "AD", "OC"]:
|
||||||
|
logger.warning(f"第 {row_num} 行 [{dev_name}]:协议类型 {dev_info['protocol']} 不合法,仅支持 UART/CAN/1553B/AD/OC,跳过该行")
|
||||||
|
continue
|
||||||
|
|
||||||
|
device_config[dev_name] = dev_info
|
||||||
|
logger.debug(
|
||||||
|
f"解析外设配置:{dev_name} | {dev_info['protocol']} | 基地址 {hex(dev_info['base_addr'])} | "
|
||||||
|
f"端口号 {dev_info['udp_port']} | 发送缓存 {dev_info['send_cache']} | 接收缓存 {dev_info['recv_cache']} | 说明 {dev_info['desc']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
|
||||||
|
print(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"配置文件不存在:{file_path}")
|
||||||
|
print(f"配置文件不存在:{file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"配置文件解析失败:{str(e)}", exc_info=True)
|
||||||
|
print(f"配置文件解析失败:{str(e)}")
|
||||||
|
finally:
|
||||||
|
# 确保工作簿最终关闭(无论是否异常)
|
||||||
|
if wb:
|
||||||
|
try:
|
||||||
|
wb.close()
|
||||||
|
logger.debug("Excel工作簿已正常关闭")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"关闭Excel工作簿失败:{str(e)}")
|
||||||
|
# 删除CSV转换的临时Excel文件
|
||||||
|
if os.path.exists(temp_excel):
|
||||||
|
try:
|
||||||
|
os.remove(temp_excel)
|
||||||
|
logger.debug("临时Excel文件已删除")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"删除临时Excel文件失败:{str(e)}")
|
||||||
|
|
||||||
|
return device_config
|
||||||
|
|
||||||
|
# 测试代码(单独运行该文件时执行)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# test_config = load_csv_config("../../config_files/device_config.csv") # 替换为实际文件路径
|
||||||
|
test_config = load_csv_config("config_files/device_config.csv") # 替换为实际文件路径,难道这的根目录
|
||||||
|
print(test_config)
|
||||||
137
midware/config/excel_config-20260226.py
Normal file
137
midware/config/excel_config-20260226.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Excel 配置解析(excel_config.py):基于openpyxl实现 Excel 文件解析,提取外设名称、UDP 端口、协议类型、基地址等信息,存入DEVICE_CONFIG_DICT。
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
### 核心功能说明
|
||||||
|
1. **格式兼容**:支持解析Excel中**带逗号/空格的十六进制地址**(如`0x2080,0000`→`0x20800000`),自动清洗非十六进制字符。
|
||||||
|
2. **列名容错**:兼容表头列名的**大小写、别名**(如“基地址”/“内存基地址”、“发送缓存”/“发送缓存区大小”)。
|
||||||
|
3. **缺省值赋值**:未配置的项赋予合理默认值(如UDP端口默认8888、缓存大小默认512)。
|
||||||
|
4. **合法性校验**:
|
||||||
|
- 必须包含**名称、协议类型、内存基地址**核心列,否则解析失败;
|
||||||
|
- 协议类型仅支持`UART/CAN/1553B/AD/OC`,非法协议直接跳过;
|
||||||
|
- 外设名称为空的行直接跳过。
|
||||||
|
5. **数据类型转换**:自动将十六进制地址转整数、端口/缓存大小转整数,转换失败友好提示并赋默认值。
|
||||||
|
6. **日志记录**:全程打印解析日志(成功/失败/警告),便于问题排查。
|
||||||
|
|
||||||
|
### 配套Excel配置文件规范
|
||||||
|
1. 保存为**xlsx格式**(兼容openpyxl解析,xls格式需额外安装xlrd);
|
||||||
|
2. 表头至少包含:**名称、协议类型、内存基地址**,其他列可选;
|
||||||
|
3. 协议类型填写:`UART/CAN/1553B/AD/OC`(大小写均可);
|
||||||
|
4. 地址填写:十六进制格式(如`0x20800000`、`0x2080,0000`、`0x0000`);
|
||||||
|
5. 端口/缓存大小填写**纯数字**。
|
||||||
|
|
||||||
|
### 简单测试
|
||||||
|
在`config_files/`目录下创建`device_config.xlsx`,填入如下测试数据,直接运行该文件即可看到解析结果:
|
||||||
|
|
||||||
|
| 名称 | 协议类型 | 内存基地址 | 内存偏移地址 | UDP端口 | 说明 | 发送缓存区大小 | 接收缓存区大小 |
|
||||||
|
|--------|----------|------------|--------------|---------|------------|----------------|----------------|
|
||||||
|
| Uart0 | UART | 0x2080,0000 | 0x0000 | 8880 | 光纤陀螺A | 512 | 512 |
|
||||||
|
| CAN1 | CAN | 0x20810000 | 0x0100 | 8881 | 姿态传感器 | 1024 | 1024 |
|
||||||
|
| 1553B0 | 1553B | 0x20820000 | 0x0200 | 8882 | 测控模块 | 2048 | 2048 |
|
||||||
|
'''
|
||||||
|
|
||||||
|
import openpyxl
|
||||||
|
from loguru import logger
|
||||||
|
import re
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
# 设置标准输出和标准错误的编码为UTF-8
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
def hex_to_int (hex_str):
|
||||||
|
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x2080,0000、0x0000 1234):param hex_str: 十六进制字符串:return: 转换后的整数,失败返回 0"""
|
||||||
|
if not isinstance (hex_str, str):
|
||||||
|
return 0 #移除逗号、空格等非十六进制字符
|
||||||
|
clean_hex = re.sub (r'[,\s]', '', hex_str.strip ())
|
||||||
|
try:
|
||||||
|
return int (clean_hex, 16) if clean_hex else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
logger.warning (f"十六进制转换失败:{hex_str},默认返回 0")
|
||||||
|
return 0
|
||||||
|
'''
|
||||||
|
"""加载 Excel 外设配置文件,解析核心配置项
|
||||||
|
:param file_path: Excel ,csv文件路径(xlsx/xls/csv)
|
||||||
|
:return: 外设配置字典,key = 外设名称,value = 配置项字典
|
||||||
|
错误处理: 配置文件不存在,配置文件解析失败
|
||||||
|
"""
|
||||||
|
'''
|
||||||
|
def load_csv_config (file_path):
|
||||||
|
|
||||||
|
device_config = {}
|
||||||
|
try:
|
||||||
|
#打开 Excel 文件,只读取第一个工作表
|
||||||
|
# 先用 pandas 读取 CSV
|
||||||
|
df = pd.read_csv(file_path)
|
||||||
|
# 保存为 Excel
|
||||||
|
df.to_excel('file.xlsx', index=False)
|
||||||
|
# 然后使用 openpyxl 读取
|
||||||
|
wb = openpyxl.load_workbook('file.xlsx', data_only=True)
|
||||||
|
|
||||||
|
#wb = openpyxl.load_workbook(file_path, data_only=True)
|
||||||
|
ws = wb.active
|
||||||
|
logger.info(f"成功打开 Excel 配置文件:{file_path},工作表:{ws.title}")
|
||||||
|
#获取表头行,匹配核心配置列(兼容列名大小写 / 空格)
|
||||||
|
|
||||||
|
header = [cell.value.strip () if cell.value else "" for cell in ws [1]]
|
||||||
|
col_mapping = {"名称": header.index ([h for h in header if h.lower () == "名称"][0]) if [h for h in header if h.lower () == "名称"] else -1,
|
||||||
|
"协议类型": header.index ([h for h in header if h.lower () in ["协议", "协议类型"]][0]) if [h for h in header if h.lower () in ["协议", "协议类型"]] else -1,
|
||||||
|
"内存基地址": header.index ([h for h in header if h.lower () in ["内存基地址", "基地址"]][0]) if [h for h in header if h.lower () in ["内存基地址", "基地址"]] else -1,
|
||||||
|
"内存偏移地址": header.index ([h for h in header if h.lower () in ["内存偏移地址", "偏移地址"]][0]) if [h for h in header if h.lower () in ["内存偏移地址", "偏移地址"]] else -1,
|
||||||
|
"UDP端口": header.index ([h for h in header if h.lower () in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]][0]) if [h for h in header if h.lower () in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]] else -1,
|
||||||
|
"说明": header.index ([h for h in header if h.lower () == "说明"][0]) if [h for h in header if h.lower () == "说明"] else -1,
|
||||||
|
"发送缓存区大小": header.index ([h for h in header if h.lower () in ["发送缓存区大小", "发送缓存"]][0]) if [h for h in header if h.lower () in ["发送缓存区大小", "发送缓存"]] else -1,
|
||||||
|
"接收缓存区大小": header.index ([h for h in header if h.lower () in ["接收缓存区大小", "接收缓存"]][0]) if [h for h in header if h.lower () in ["接收缓存区大小", "接收缓存"]] else -1,
|
||||||
|
}
|
||||||
|
# 校验核心列(名称、协议、基地址必须存在)
|
||||||
|
required_cols = ["名称", "协议类型", "内存基地址"]
|
||||||
|
for col in required_cols:
|
||||||
|
if col_mapping [col] == -1:
|
||||||
|
logger.error (f"Excel 配置文件缺少核心列:{col},请检查表头")
|
||||||
|
# wb.close ()
|
||||||
|
return device_config
|
||||||
|
#遍历数据行(从第 2 行开始)
|
||||||
|
|
||||||
|
for row_num, row in enumerate (ws.iter_rows (min_row=2, values_only=True), start=2):
|
||||||
|
if not row [col_mapping ["名称"]]: # 外设名称为空则跳过
|
||||||
|
logger.warning (f"第 {row_num} 行:外设名称为空,跳过该行")
|
||||||
|
continue
|
||||||
|
dev_name = row [col_mapping ["名称"]].strip ()
|
||||||
|
#解析单行配置,缺省项赋默认值
|
||||||
|
dev_info = {"protocol": row [col_mapping ["协议类型"]].strip ().upper () if row [col_mapping ["协议类型"]] else "",
|
||||||
|
"base_addr": hex_to_int (row [col_mapping ["内存基地址"]]),
|
||||||
|
"offset_addr": hex_to_int (row [col_mapping ["内存偏移地址"]]),
|
||||||
|
"udp_port": int (row [col_mapping ["UDP端口"]]) if (row [col_mapping ["UDP端口"]] and str (row [col_mapping ["UDP端口"]]).isdigit ()) else 8888,
|
||||||
|
"desc": row [col_mapping ["说明"]].strip () if row [col_mapping ["说明"]] else "无",
|
||||||
|
"send_cache": int (row [col_mapping ["发送缓存区大小"]]) if (row [col_mapping ["发送缓存区大小"]] and str (row [col_mapping ["发送缓存区大小"]]).isdigit ()) else 512,
|
||||||
|
"recv_cache": int (row [col_mapping ["接收缓存区大小"]]) if (row [col_mapping ["接收缓存区大小"]] and str (row [col_mapping ["接收缓存区大小"]]).isdigit ()) else 512,
|
||||||
|
"enable": True # 默认启用该外设
|
||||||
|
}
|
||||||
|
#校验协议类型合法性
|
||||||
|
if dev_info ["protocol"] not in ["UART", "CAN", "1553B", "AD", "OC"]:
|
||||||
|
logger.warning (f"第 {row_num} 行 [{dev_name}]:协议类型 {dev_info ['protocol']} 不合法,仅支持 UART/CAN/1553B/AD/OC,跳过该行")
|
||||||
|
continue
|
||||||
|
device_config [dev_name] = dev_info
|
||||||
|
logger.debug (f"解析外设配置:{dev_name} | {dev_info ['protocol']} | 基地址 {hex (dev_info ['base_addr'])} | 端口号 {dev_info ['udp_port']} | 发送缓存 {dev_info ['send_cache']} | 接收缓存 {dev_info ['recv_cache']} | 说明 {dev_info ['desc']}")
|
||||||
|
wb.close()
|
||||||
|
logger.info(f"Excel 配置文件解析完成,共加载 {len (device_config)} 个有效外设配置")
|
||||||
|
print(f"Excel 配置文件解析完成,共加载 {len (device_config)} 个有效外设配置")
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error (f"Excel 配置文件不存在:{file_path}")
|
||||||
|
print(f"Excel 配置文件不存在:{file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error (f"Excel 配置文件解析失败:{str (e)}", exc_info=True)
|
||||||
|
print(f"Excel 配置文件解析失败:{str (e)}", exc_info=True)
|
||||||
|
return device_config
|
||||||
|
#测试代码(单独运行该文件时执行)
|
||||||
|
# if name == "main":
|
||||||
|
# test_config = load_excel_config("../../config_files/device_config.xlsx")
|
||||||
|
# print(test_config)
|
||||||
|
|
||||||
|
# 测试代码(单独运行该文件时执行)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_config = load_csv_config("../../config_files/device_config.csv") # 替换为实际文件路径
|
||||||
|
print(test_config)
|
||||||
155
midware/config/excel_config.py
Normal file
155
midware/config/excel_config.py
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Excel 配置解析(excel_config.py):基于openpyxl实现 Excel 文件解析,提取外设名称、UDP 端口、协议类型、基地址等信息,存入DEVICE_CONFIG_DICT。
|
||||||
|
'''
|
||||||
|
import openpyxl
|
||||||
|
from loguru import logger
|
||||||
|
import re
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
|
||||||
|
# 设置标准输出和标准错误的编码为UTF-8
|
||||||
|
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
# sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
def hex_to_int (hex_str):
|
||||||
|
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x2080,0000、0x0000 1234)
|
||||||
|
:param hex_str: 十六进制字符串
|
||||||
|
:return: 转换后的整数,失败返回 0
|
||||||
|
"""
|
||||||
|
if not isinstance (hex_str, str):
|
||||||
|
return 0 # 非字符串直接返回0
|
||||||
|
# 移除逗号、空格等非十六进制字符
|
||||||
|
clean_hex = re.sub (r'[,\s]', '', hex_str.strip ())
|
||||||
|
try:
|
||||||
|
return int (clean_hex, 16) if clean_hex else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
logger.warning (f"十六进制转换失败:{hex_str},默认返回 0")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def load_csv_config (file_path):
|
||||||
|
"""加载 Excel/CSV 外设配置文件,解析核心配置项
|
||||||
|
:param file_path: Excel/CSV文件路径(xlsx/xls/csv)
|
||||||
|
:return: 外设配置字典,key = 外设名称,value = 配置项字典
|
||||||
|
"""
|
||||||
|
device_config = {}
|
||||||
|
wb = None
|
||||||
|
temp_excel = "file.xlsx"
|
||||||
|
is_temp_file = False # 标记是否生成了临时文件
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 处理CSV文件:转换为Excel后解析
|
||||||
|
if file_path.endswith('.csv'):
|
||||||
|
df = pd.read_csv(file_path)
|
||||||
|
df.to_excel(temp_excel, index=False)
|
||||||
|
file_path = temp_excel
|
||||||
|
is_temp_file = True # 标记临时文件已生成
|
||||||
|
|
||||||
|
# 打开工作簿(兼容直接传入Excel文件的情况)
|
||||||
|
wb = openpyxl.load_workbook(file_path, data_only=True)
|
||||||
|
ws = wb.active
|
||||||
|
logger.info(f"成功打开配置文件:{file_path},工作表:{ws.title}")
|
||||||
|
|
||||||
|
# 获取表头行,匹配核心配置列(兼容列名大小写 / 空格)
|
||||||
|
header = [cell.value.strip() if cell.value else "" for cell in ws[1]]
|
||||||
|
col_mapping = {
|
||||||
|
"名称": header.index([h for h in header if h.lower() == "名称"][0]) if [h for h in header if h.lower() == "名称"] else -1,
|
||||||
|
"编号": header.index([h for h in header if h.lower() == "编号"][0]) if [h for h in header if h.lower() == "编号"] else -1,
|
||||||
|
"协议类型": header.index([h for h in header if h.lower() in ["协议", "协议类型"]][0]) if [h for h in header if h.lower() in ["协议", "协议类型"]] else -1,
|
||||||
|
"内存基地址": header.index([h for h in header if h.lower() in ["内存基地址", "基地址"]][0]) if [h for h in header if h.lower() in ["内存基地址", "基地址"]] else -1,
|
||||||
|
"内存偏移地址": header.index([h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]][0]) if [h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]] else -1,
|
||||||
|
"UDP端口": header.index([h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]][0]) if [h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]] else -1,
|
||||||
|
"UDP远程端口": header.index([h for h in header if h.lower() in ["UDP远程端口", "远程端口地址", "远程udp 地址", "remote_port","remote port"]][0]) if [h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "remote_port","remote port"]] else -1,
|
||||||
|
"TCP端口": header.index([h for h in header if h.lower() in ["TCP端口", "端口地址", "TCP 地址", "tcp_local_port"]][0]) if [h for h in header if h.lower() in ["TCP端口", "tcp_local_port"]] else -1,
|
||||||
|
"TCP总线端口": header.index([h for h in header if h.lower() in ["TCP远程端口", "远程端口地址", "远程tcp 地址", "tcp_bus_port"]][0]) if [h for h in header if h.lower() in ["TCP端口", "tcp_bus_port"]] else -1,
|
||||||
|
"说明": header.index([h for h in header if h.lower() == "说明"][0]) if [h for h in header if h.lower() == "说明"] else -1,
|
||||||
|
"发送缓存区大小": header.index([h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]][0]) if [h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]] else -1,
|
||||||
|
"接收缓存区大小": header.index([h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]][0]) if [h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]] else -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 校验核心列(名称、协议、基地址必须存在)
|
||||||
|
required_cols = ["名称", "协议类型", "内存基地址"]
|
||||||
|
missing_cols = [col for col in required_cols if col_mapping[col] == -1]
|
||||||
|
if missing_cols:
|
||||||
|
logger.error(f"配置文件缺少核心列:{','.join(missing_cols)},请检查表头")
|
||||||
|
return device_config
|
||||||
|
|
||||||
|
# 关键优化:将迭代器转为列表,提前加载所有行数据(避免后续关闭wb后访问)
|
||||||
|
rows_data = list(ws.iter_rows(min_row=2, values_only=True))
|
||||||
|
|
||||||
|
# 遍历数据行(从第 2 行开始)
|
||||||
|
for row_num, row in enumerate(rows_data, start=2):
|
||||||
|
if not row[col_mapping["名称"]]: # 外设名称为空则跳过
|
||||||
|
logger.warning(f"第 {row_num} 行:外设名称为空,跳过该行")
|
||||||
|
continue
|
||||||
|
|
||||||
|
dev_name = row[col_mapping["名称"]].strip()
|
||||||
|
print(f'原始值:{row[col_mapping["编号"]]}')
|
||||||
|
print(f'类型:{type(row[col_mapping["编号"]])}')
|
||||||
|
print(str(row[col_mapping["编号"]]))
|
||||||
|
str_value = str(row[col_mapping["编号"]])
|
||||||
|
print(f'isdigit?:{str_value.isdigit()}')
|
||||||
|
# 解析单行配置,缺省项赋默认值
|
||||||
|
dev_info = {
|
||||||
|
"protocol": row[col_mapping["协议类型"]].strip().upper() if row[col_mapping["协议类型"]] else "",
|
||||||
|
"number": int(row[col_mapping["编号"]]) if (row[col_mapping["编号"]] is not None and str(row[col_mapping["编号"]]).isdigit()) else 99,#2026/4/15
|
||||||
|
"base_addr": hex_to_int(row[col_mapping["内存基地址"]]),
|
||||||
|
"offset_addr": hex_to_int(row[col_mapping["内存偏移地址"]]),
|
||||||
|
"udp_port": int(row[col_mapping["UDP端口"]]) if (row[col_mapping["UDP端口"]] and str(row[col_mapping["UDP端口"]]).isdigit()) else 8888,
|
||||||
|
"remote_port": int(row[col_mapping["UDP远程端口"]]) if (row[col_mapping["UDP远程端口"]] and str(row[col_mapping["UDP远程端口"]]).isdigit()) else 9999,
|
||||||
|
"tcp_local_port": int(row[col_mapping["TCP端口"]]) if (row[col_mapping["TCP端口"]] and str(row[col_mapping["TCP端口"]]).isdigit()) else 1,
|
||||||
|
"tcp_bus_port": int(row[col_mapping["TCP总线端口"]]) if (row[col_mapping["TCP总线端口"]] and str(row[col_mapping["TCP总线端口"]]).isdigit()) else 1,
|
||||||
|
"desc": row[col_mapping["说明"]].strip() if row[col_mapping["说明"]] else "无",
|
||||||
|
"send_cache": int(row[col_mapping["发送缓存区大小"]]) if (row[col_mapping["发送缓存区大小"]] and str(row[col_mapping["发送缓存区大小"]]).isdigit()) else 512,
|
||||||
|
"recv_cache": int(row[col_mapping["接收缓存区大小"]]) if (row[col_mapping["接收缓存区大小"]] and str(row[col_mapping["接收缓存区大小"]]).isdigit()) else 512,
|
||||||
|
"enable": True # 默认启用该外设
|
||||||
|
}
|
||||||
|
|
||||||
|
# 校验协议类型合法性
|
||||||
|
if dev_info["protocol"] not in ["UART", "CAN", "1553B", "AD", "OC"]:
|
||||||
|
logger.warning(f"第 {row_num} 行 [{dev_name}]:协议类型 {dev_info['protocol']} 不合法,仅支持 UART/CAN/1553B/AD/OC,跳过该行")
|
||||||
|
continue
|
||||||
|
|
||||||
|
device_config[dev_name] = dev_info
|
||||||
|
logger.debug(
|
||||||
|
f"解析外设配置:{dev_name} | {dev_info['protocol']} | 基地址 {hex(dev_info['base_addr'])} | "
|
||||||
|
f"端口号 {dev_info['udp_port']} | 发送缓存 {dev_info['send_cache']} | 接收缓存 {dev_info['recv_cache']} | 说明 {dev_info['desc']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
|
||||||
|
print(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"配置文件不存在:{file_path}")
|
||||||
|
print(f"配置文件不存在:{file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"配置文件解析失败:{str(e)}", exc_info=True)
|
||||||
|
print(f"配置文件解析失败:{str(e)}")
|
||||||
|
finally:
|
||||||
|
# 第一步:先关闭工作簿(确保所有数据已读取完成)
|
||||||
|
if wb:
|
||||||
|
try:
|
||||||
|
wb.close()
|
||||||
|
logger.debug("Excel工作簿已正常关闭")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"关闭Excel工作簿失败:{str(e)}")
|
||||||
|
|
||||||
|
# 第二步:删除临时文件(仅当生成了临时文件时执行)
|
||||||
|
if is_temp_file and os.path.exists(temp_excel):
|
||||||
|
try:
|
||||||
|
# 延迟删除(避免文件句柄未释放)
|
||||||
|
import time
|
||||||
|
time.sleep(0.1)
|
||||||
|
os.remove(temp_excel)
|
||||||
|
logger.debug("临时Excel文件已删除")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"删除临时Excel文件失败:{str(e)}")
|
||||||
|
|
||||||
|
return device_config
|
||||||
|
|
||||||
|
# 测试代码(单独运行该文件时执行)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_config = load_csv_config("config_files/device_config.csv")
|
||||||
|
print(test_config)
|
||||||
23
midware/core/__init__.py
Normal file
23
midware/core/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# midware/core/__init__.py
|
||||||
|
"""
|
||||||
|
核心业务层:总线-UDP解耦、总线驱动、数据处理
|
||||||
|
"""
|
||||||
|
# from .bus_udp_decoupler import (
|
||||||
|
# start_all_threads,
|
||||||
|
# stop_all_threads,
|
||||||
|
# protocol_dispatch_queue,
|
||||||
|
# bus_queues,
|
||||||
|
# udp_send_queue
|
||||||
|
# )
|
||||||
|
|
||||||
|
from .bus_udp_decoupler import (
|
||||||
|
start_all_threads,
|
||||||
|
stop_all_threads,
|
||||||
|
bus_recv_queues,
|
||||||
|
bus_send_queues,
|
||||||
|
udp_send_queue,
|
||||||
|
udp_recv_dispatch_queue
|
||||||
|
)
|
||||||
|
# 暴露版本/作者等元信息(可选)
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "xxx"
|
||||||
BIN
midware/core/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/core/__pycache__/bus_udp_decoupler.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/bus_udp_decoupler.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
midware/core/__pycache__/data_forward.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/data_forward.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/core/__pycache__/error_check.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/error_check.cpython-311.pyc
Normal file
Binary file not shown.
0
midware/core/bus_drivers/1553b_driver.py
Normal file
0
midware/core/bus_drivers/1553b_driver.py
Normal file
0
midware/core/bus_drivers/__init__.py
Normal file
0
midware/core/bus_drivers/__init__.py
Normal file
0
midware/core/bus_drivers/ad_driver.py
Normal file
0
midware/core/bus_drivers/ad_driver.py
Normal file
78
midware/core/bus_drivers/base_driver.py
Normal file
78
midware/core/bus_drivers/base_driver.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# midware/drivers/base_driver.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
驱动基类(base_driver.py)—— 定义标准接口(核心)
|
||||||
|
所有总线驱动继承该基类,保证接口统一,解耦 bus_udp_decoupler.py 与具体驱动实现:
|
||||||
|
"""
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
class BaseBusDriver(ABC):
|
||||||
|
"""
|
||||||
|
总线驱动基类:定义所有总线必须实现的标准接口
|
||||||
|
遵循「开闭原则」:新增总线只需继承该类实现接口,无需修改现有代码
|
||||||
|
"""
|
||||||
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||||
|
"""
|
||||||
|
初始化驱动
|
||||||
|
:param config: 驱动配置(如波特率、端口、IP等)
|
||||||
|
"""
|
||||||
|
self.config = config or {}
|
||||||
|
self.is_connected = False # 驱动连接状态
|
||||||
|
self._init_config() # 初始化配置
|
||||||
|
|
||||||
|
def _init_config(self):
|
||||||
|
"""初始化默认配置(子类可重写)"""
|
||||||
|
logger.info(f"初始化 {self.__class__.__name__} 默认配置")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def connect(self) -> bool:
|
||||||
|
"""
|
||||||
|
连接总线(如打开串口、建立CAN通道、连接1553B板卡)
|
||||||
|
:return: 连接成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
"""
|
||||||
|
断开总线连接
|
||||||
|
:return: 断开成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
"""
|
||||||
|
发送数据到总线
|
||||||
|
:param data: 待发送的原始字节数据
|
||||||
|
:param kwargs: 扩展参数(如优先级、地址、帧ID等)
|
||||||
|
:return: 发送成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
"""
|
||||||
|
从总线接收数据
|
||||||
|
:param timeout: 接收超时时间(秒)
|
||||||
|
:param kwargs: 扩展参数(如过滤条件、地址等)
|
||||||
|
:return: 接收到的字节数据,超时/失败返回None
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_status(self) -> bool:
|
||||||
|
"""
|
||||||
|
检查驱动状态(默认实现,子类可重写)
|
||||||
|
:return: 驱动正常返回True,异常返回False
|
||||||
|
"""
|
||||||
|
return self.is_connected
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""析构函数:自动断开连接"""
|
||||||
|
if self.is_connected:
|
||||||
|
self.disconnect()
|
||||||
|
logger.info(f"{self.__class__.__name__} 自动断开连接")
|
||||||
0
midware/core/bus_drivers/can_driver.py
Normal file
0
midware/core/bus_drivers/can_driver.py
Normal file
0
midware/core/bus_drivers/oc_driver.py
Normal file
0
midware/core/bus_drivers/oc_driver.py
Normal file
0
midware/core/bus_drivers/uart_driver.py
Normal file
0
midware/core/bus_drivers/uart_driver.py
Normal file
637
midware/core/bus_udp_decoupler.py
Normal file
637
midware/core/bus_udp_decoupler.py
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tan Mintgyan
|
||||||
|
2026/3/9
|
||||||
|
总线-UDP双向通信解耦模块(新增带协议优先级:1553B>CAN>UART>AD>OC>网络)
|
||||||
|
"""
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
from loguru import logger
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
from midware.drivers import create_bus_driver
|
||||||
|
import midware.config.base_config as base_config
|
||||||
|
|
||||||
|
from ..drivers.renode_agent import renode
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 1. 核心:定义协议优先级映射(数值越小优先级越高) ==========
|
||||||
|
PROTOCOL_PRIORITY = {
|
||||||
|
"1553B": 1, # 最高优先级
|
||||||
|
"CAN": 2,
|
||||||
|
"UART": 3,
|
||||||
|
"AD": 4,
|
||||||
|
"OC": 5,
|
||||||
|
"网络": 6 # 最低优先级
|
||||||
|
}
|
||||||
|
MAX_QUEUE_SIZE = 1000 # 队列最大长度(可按协议单独调整)
|
||||||
|
|
||||||
|
# ========== 2. 收发队列分离(发送队列统一用PriorityQueue) ==========
|
||||||
|
# 总线接收队列(总线→中间件:上行数据,无需优先级)
|
||||||
|
bus_recv_queues = {
|
||||||
|
"1553B": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"CAN": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"UART": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"AD": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"OC": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"网络": queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 总线发送队列(中间件→总线:下行数据,带优先级)
|
||||||
|
# 统一使用PriorityQueue,按PROTOCOL_PRIORITY的数值排序
|
||||||
|
bus_send_queues = {
|
||||||
|
"1553B": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"CAN": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"UART": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"AD": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"OC": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"网络": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
# UDP发送队列(中间件→UDP:上行数据)
|
||||||
|
udp_send_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
# UDP接收分发队列(UDP→中间件:下行数据)
|
||||||
|
udp_recv_dispatch_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
|
||||||
|
# ========== 3. UDP接收线程:自动映射协议优先级 ==========
|
||||||
|
def udp_recv_thread(udp_server):
|
||||||
|
"""UDP接收线程:接收下行指令,自动分配优先级后放入总线发送队列"""
|
||||||
|
logger.info("UDP接收线程(下行指令)启动")
|
||||||
|
|
||||||
|
##测试用
|
||||||
|
try:
|
||||||
|
logger.debug("测试队列入队")
|
||||||
|
print("测试队列入队")
|
||||||
|
protocol= 'AD'
|
||||||
|
dev_name = 'adc1',#2026/4/9
|
||||||
|
#dev_name = '正电压采集热敏量采集',
|
||||||
|
raw_data = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'#56*2=112字节
|
||||||
|
priority = 3
|
||||||
|
bus_send_queues[protocol].put_nowait((
|
||||||
|
priority, # 按数值从小到大优先
|
||||||
|
{
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": raw_data,
|
||||||
|
"protocol": protocol,
|
||||||
|
"priority": priority,
|
||||||
|
"recv_time": time.time() # 记录接收时间
|
||||||
|
}
|
||||||
|
))
|
||||||
|
logger.debug(
|
||||||
|
f"下行指令入队|{protocol}(优先级{priority})|{dev_name}|"
|
||||||
|
f"队列长度:{bus_send_queues[protocol].qsize()}"
|
||||||
|
)
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(
|
||||||
|
f"{protocol}(优先级{priority})发送队列已满,丢弃 {dev_name} 下行指令"
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
recv_data = udp_server.recv_multi_udp_hex()
|
||||||
|
if recv_data:
|
||||||
|
for data in recv_data:
|
||||||
|
protocol = data.get("protocol", "").strip().upper()
|
||||||
|
dev_name = data.get("dev_name", "")
|
||||||
|
raw_data = data.get("raw_data", b"")
|
||||||
|
recv_time = time.time()
|
||||||
|
# 核心:自动获取协议优先级(未知协议设为最低优先级7)
|
||||||
|
priority = PROTOCOL_PRIORITY.get(protocol, 7)
|
||||||
|
|
||||||
|
if protocol not in bus_send_queues:
|
||||||
|
logger.warning(f"未知协议 {protocol},丢弃下行指令:{dev_name}(优先级:{priority})")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 放入总线发送队列(PriorityQueue格式:(优先级数值, 数据))
|
||||||
|
try:
|
||||||
|
bus_send_queues[protocol].put_nowait((
|
||||||
|
priority, # 按数值从小到大优先
|
||||||
|
#recv_time,#新增,用来打破优先级相同的平局
|
||||||
|
{
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": raw_data,
|
||||||
|
"protocol": protocol,
|
||||||
|
"priority": priority,
|
||||||
|
"recv_time": recv_time # 记录接收时间
|
||||||
|
}
|
||||||
|
))
|
||||||
|
logger.debug(
|
||||||
|
f"下行指令入队|{protocol}(优先级{priority})|{dev_name}|"
|
||||||
|
f"队列长度:{bus_send_queues[protocol].qsize()}"
|
||||||
|
)
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(
|
||||||
|
f"{protocol}(优先级{priority})发送队列已满,丢弃 {dev_name} 下行指令"
|
||||||
|
)
|
||||||
|
time.sleep(1e-6)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 4. 总线发送线程:按优先级消费数据 ==========
|
||||||
|
'''
|
||||||
|
def bus_send_thread(protocol: str, bus_driver: Optional[Any] = None):#2026/3/9停用
|
||||||
|
"""总线发送线程:优先消费高优先级数据(数值越小越先处理)"""
|
||||||
|
logger.info(f"{protocol} 总线发送线程启动(优先级:{PROTOCOL_PRIORITY.get(protocol, 7)})")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# PriorityQueue自动按优先级取值(数值小的先出)
|
||||||
|
priority, send_data = bus_send_queues[protocol].get(timeout=1)
|
||||||
|
dev_name = send_data["dev_name"]
|
||||||
|
raw_data = send_data["raw_data"]
|
||||||
|
recv_time = send_data["recv_time"]
|
||||||
|
|
||||||
|
# 超时检查(可选:超过5秒未发送则丢弃)
|
||||||
|
if time.time() - recv_time > 5:
|
||||||
|
logger.warning(
|
||||||
|
f"{protocol}(优先级{priority})|{dev_name} 指令超时(5秒),丢弃"
|
||||||
|
)
|
||||||
|
bus_send_queues[protocol].task_done()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 核心:总线发送逻辑(替换为真实驱动调用)
|
||||||
|
logger.info(
|
||||||
|
f"总线发送|{protocol}(优先级{priority})|{dev_name}|"
|
||||||
|
f"数据:{raw_data.hex()}|队列剩余:{bus_send_queues[protocol].qsize()}"
|
||||||
|
)
|
||||||
|
# if bus_driver:
|
||||||
|
# bus_driver.send(raw_data) # 调用总线驱动发送
|
||||||
|
|
||||||
|
# 标记任务完成
|
||||||
|
bus_send_queues[protocol].task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 发送线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# ========== 修正:总线发送线程(参数改为接收驱动配置) ==========
|
||||||
|
def bus_send_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
|
||||||
|
"""
|
||||||
|
总线发送线程:通过统一驱动接口发送数据
|
||||||
|
:param protocol: 协议名称
|
||||||
|
:param bus_driver_config: 驱动配置字典
|
||||||
|
"""
|
||||||
|
logger.info(f"{protocol} 总线发送线程启动(优先级:{PROTOCOL_PRIORITY.get(protocol, 7)})")
|
||||||
|
|
||||||
|
# 创建驱动实例
|
||||||
|
try:
|
||||||
|
driver = create_bus_driver(protocol, config=bus_driver_config)
|
||||||
|
# 连接总线
|
||||||
|
if not driver.connect():
|
||||||
|
logger.error(f"{protocol} 驱动连接失败,线程退出")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
priority, send_data = bus_send_queues[protocol].get(timeout=1)
|
||||||
|
dev_name = send_data["dev_name"]
|
||||||
|
raw_data = send_data["raw_data"]
|
||||||
|
recv_time = send_data["recv_time"]
|
||||||
|
|
||||||
|
'''# 超时检查
|
||||||
|
if time.time() - recv_time > 5:
|
||||||
|
logger.warning(f"{protocol}(优先级{priority})|{dev_name} 指令超时,丢弃")
|
||||||
|
bus_send_queues[protocol].task_done()
|
||||||
|
continue
|
||||||
|
'''
|
||||||
|
# 调用驱动发送(统一接口)
|
||||||
|
send_success = driver.send(
|
||||||
|
data=raw_data,
|
||||||
|
priority=priority, # 传递优先级参数
|
||||||
|
dev_name=dev_name # 传递设备名
|
||||||
|
)
|
||||||
|
|
||||||
|
if send_success:
|
||||||
|
logger.info(
|
||||||
|
f"{protocol}(优先级{priority})|{dev_name} 发送成功|"
|
||||||
|
f"数据:{raw_data.hex()}|队列剩余:{bus_send_queues[protocol].qsize()}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.error(f"{protocol}(优先级{priority})|{dev_name} 发送失败")
|
||||||
|
|
||||||
|
bus_send_queues[protocol].task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 发送线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
# 线程退出时断开驱动(析构函数也会自动处理)
|
||||||
|
driver.disconnect()
|
||||||
|
|
||||||
|
# ========== 修正:start_all_threads 函数(适配 bus_driver_configs 参数) ==========
|
||||||
|
def start_all_threads(udp_server, bus_driver_configs: Optional[Dict[str, Any]] = None):
|
||||||
|
"""
|
||||||
|
启动所有线程
|
||||||
|
:param udp_server: UDP服务实例
|
||||||
|
:param bus_driver_configs: 驱动配置字典,格式:{"1553B": {...}, "CAN": {...}, ...}
|
||||||
|
"""
|
||||||
|
threads = []
|
||||||
|
bus_driver_configs = bus_driver_configs or {} # 无配置时设为空字典
|
||||||
|
|
||||||
|
# 按优先级从高到低排序协议
|
||||||
|
sorted_protos = sorted(PROTOCOL_PRIORITY.keys(), key=lambda x: PROTOCOL_PRIORITY[x])
|
||||||
|
|
||||||
|
# 1. UDP接收线程
|
||||||
|
udp_recv_t = threading.Thread(target=udp_recv_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_recv_t)
|
||||||
|
|
||||||
|
# 2. 总线发送线程(按优先级排序启动,传递对应驱动配置)
|
||||||
|
for protocol in sorted_protos:
|
||||||
|
# 获取当前协议的驱动配置
|
||||||
|
driver_config = bus_driver_configs.get(protocol, {})
|
||||||
|
send_t = threading.Thread(
|
||||||
|
target=bus_send_thread,
|
||||||
|
args=(protocol, driver_config), # 传递协议名+驱动配置
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
threads.append(send_t)
|
||||||
|
'''
|
||||||
|
# 3. 总线接收线程(按优先级排序启动,传递对应驱动配置)
|
||||||
|
for protocol in sorted_protos:
|
||||||
|
driver_config = bus_driver_configs.get(protocol, {})
|
||||||
|
recv_t = threading.Thread(
|
||||||
|
target=bus_recv_thread,
|
||||||
|
args=(protocol, driver_config), # 传递协议名+驱动配置
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
threads.append(recv_t)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# 4. 总线接收分发线程
|
||||||
|
recv_dispatch_t = threading.Thread(target=bus_recv_dispatch_thread, daemon=True)
|
||||||
|
threads.append(recv_dispatch_t)
|
||||||
|
|
||||||
|
# 5. UDP发送线程
|
||||||
|
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_send_t)
|
||||||
|
|
||||||
|
#6. 总线轮询线程
|
||||||
|
for protocol in sorted_protos:
|
||||||
|
driver_config = bus_driver_configs.get(protocol, {})
|
||||||
|
daq_t = threading.Thread(
|
||||||
|
target=bus_daq_thread,
|
||||||
|
args=(protocol, driver_config), # 传递协议名+驱动配置
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
threads.append(daq_t)
|
||||||
|
|
||||||
|
# 启动所有线程
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
logger.info(f"线程 {t.name} 启动成功")
|
||||||
|
return threads
|
||||||
|
|
||||||
|
# ========== 其他函数(stop_all_threads、udp_recv_thread等)保持不变 ==========
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 5. 总线接收线程(无优先级,保持原有逻辑) ==========
|
||||||
|
# ========== 修正:总线接收线程(参数改为接收驱动配置) ==========
|
||||||
|
def bus_recv_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
|
||||||
|
"""总线接收线程:通过统一驱动接口接收数据"""
|
||||||
|
logger.info(f"{protocol} 总线接收线程启动")
|
||||||
|
|
||||||
|
# 创建驱动实例
|
||||||
|
try:
|
||||||
|
driver = create_bus_driver(protocol, config=bus_driver_config)
|
||||||
|
if not driver.connect():
|
||||||
|
logger.error(f"{protocol} 驱动连接失败,线程退出")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
|
||||||
|
return
|
||||||
|
|
||||||
|
time.sleep(0.001)#5->0.5
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
'''
|
||||||
|
##循环发送读取请求
|
||||||
|
# if(protocol=="UART"):
|
||||||
|
# sysbus_cmd = f'uart24 GetTXFIFODataString'
|
||||||
|
# print(sysbus_cmd)
|
||||||
|
# response = renode.send_sync(sysbus_cmd)#2026/4/10
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
##循环发送读取请求
|
||||||
|
if(protocol == "UART"):#发现protocol == "CAN"时候循环更快
|
||||||
|
sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
|
||||||
|
logger.info(f'uart0轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=10)#2026/4/10
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
if(protocol == "CAN"):
|
||||||
|
sysbus_cmd = f'can_a GetTxBufferDataString'#CAN_A
|
||||||
|
#sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
|
||||||
|
logger.info(f'can_a轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=10)#2026/4/10
|
||||||
|
time.sleep(0.25)
|
||||||
|
'''
|
||||||
|
# 调用驱动接收(统一接口)
|
||||||
|
# raw_data = driver.recv(timeout=0.001) # 短超时,非阻塞
|
||||||
|
## 以下代码没有作用,因为recv接口没有实现
|
||||||
|
'''
|
||||||
|
if raw_data and len(raw_data) > 0:
|
||||||
|
dev_name = f"{protocol}_DEV"
|
||||||
|
try:
|
||||||
|
bus_recv_queues[protocol].put_nowait({
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": raw_data,
|
||||||
|
"protocol": protocol,
|
||||||
|
"recv_time": time.time()
|
||||||
|
})
|
||||||
|
logger.debug(f"{protocol} 接收:{dev_name} 上行数据,队列长度:{bus_recv_queues[protocol].qsize()}")
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"{protocol} 接收队列已满,丢弃上行数据")
|
||||||
|
time.sleep(1e-6)
|
||||||
|
'''
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
driver.disconnect()
|
||||||
|
'''
|
||||||
|
def bus_recv_thread(protocol: str, bus_driver: Optional[Any] = None):#2026/3/9 停用
|
||||||
|
"""总线接收线程:接收上行数据,放入接收队列"""
|
||||||
|
logger.info(f"{protocol} 总线接收线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 模拟总线接收(替换为真实驱动的recv接口)
|
||||||
|
# raw_data = bus_driver.recv()
|
||||||
|
raw_data = b"" # 占位
|
||||||
|
|
||||||
|
if raw_data:
|
||||||
|
dev_name = f"{protocol}_DEV"
|
||||||
|
try:
|
||||||
|
bus_recv_queues[protocol].put_nowait({
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": raw_data,
|
||||||
|
"protocol": protocol,
|
||||||
|
"recv_time": time.time()
|
||||||
|
})
|
||||||
|
logger.debug(
|
||||||
|
f"总线接收|{protocol}|{dev_name}|"
|
||||||
|
f"队列长度:{bus_recv_queues[protocol].qsize()}"
|
||||||
|
)
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"{protocol} 接收队列已满,丢弃上行数据")
|
||||||
|
time.sleep(1e-6)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# ========== 5.5 总线轮询线程(无优先级,保持原有逻辑) ==========
|
||||||
|
def bus_daq_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
|
||||||
|
"""总线接收线程:通过统一驱动接口接收数据"""
|
||||||
|
logger.info(f"{protocol} 总线接收线程启动")
|
||||||
|
|
||||||
|
# 创建驱动实例
|
||||||
|
try:
|
||||||
|
driver = create_bus_driver(protocol, config=bus_driver_config)
|
||||||
|
if not driver.connect():
|
||||||
|
logger.error(f"{protocol} 驱动连接失败,线程退出")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
|
||||||
|
return
|
||||||
|
|
||||||
|
time.sleep(0.001)#5->0.5
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
##循环发送读取请求
|
||||||
|
# if(protocol=="UART"):
|
||||||
|
# sysbus_cmd = f'uart24 GetTXFIFODataString'
|
||||||
|
# print(sysbus_cmd)
|
||||||
|
# response = renode.send_sync(sysbus_cmd)#2026/4/10
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
##循环发送读取请求
|
||||||
|
if(protocol == "CAN"):#发现protocol == "CAN"时候循环更快
|
||||||
|
sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
|
||||||
|
logger.info(f'uart0轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=5)#2026/4/10
|
||||||
|
# response = renode.enqueue_cmd(sysbus_cmd)#2026/4/10
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
sysbus_cmd = f'uart18 GetTXFIFODataString'#uart 0
|
||||||
|
logger.info(f'uart18 轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=5)#2026/4/10
|
||||||
|
# response = renode.enqueue_cmd(sysbus_cmd)#2026/4/10
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
|
||||||
|
if(protocol == "CAN"):
|
||||||
|
sysbus_cmd = f'can_a GetTxBufferDataString'#CAN_A
|
||||||
|
#sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
|
||||||
|
logger.info(f'can_a轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
|
||||||
|
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=5)#2026/4/10
|
||||||
|
#response = renode.enqueue_cmd(sysbus_cmd)#2026/4/10
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
# 调用驱动接收(统一接口)
|
||||||
|
raw_data = driver.recv(timeout=0.001) # 短超时,非阻塞
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
driver.disconnect()
|
||||||
|
|
||||||
|
import time
|
||||||
|
'''
|
||||||
|
# ========== 5.5 总线轮询线程(无优先级,保持原有逻辑) ==========
|
||||||
|
def bus_daq_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
|
||||||
|
logger.info(f"{protocol}总线轮询线程启动")
|
||||||
|
# 创建驱动实例
|
||||||
|
try:
|
||||||
|
driver = create_bus_driver(protocol, config=bus_driver_config)
|
||||||
|
if not driver.connect():
|
||||||
|
logger.error(f"{protocol} 驱动连接失败,线程退出")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
|
||||||
|
return
|
||||||
|
time.sleep(5)
|
||||||
|
# ==================原来的while循环删除==============#
|
||||||
|
poll_period = 0.25
|
||||||
|
next_run_time = time.perf_counter()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if protocol == "UART":
|
||||||
|
sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
|
||||||
|
logger.info(f'uart0轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=1)#2026/4/10
|
||||||
|
elif protocol =="CAN":
|
||||||
|
sysbus_cmd = f'can_a GetTxBufferDataString'#CAN_A
|
||||||
|
logger.info(f'can_a轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=1)#2026/4/10
|
||||||
|
next_run_time = poll_period + next_run_time
|
||||||
|
sleep_time = next_run_time- time.perf_counter()
|
||||||
|
if sleep_time >0:
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
#如果小于0,代表超时,
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.01)
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 6. 总线接收分发线程(无优先级,保持原有逻辑) ==========
|
||||||
|
def bus_recv_dispatch_thread():
|
||||||
|
"""汇总总线接收数据,转发到UDP发送队列"""
|
||||||
|
logger.info("总线接收分发线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
for protocol, q in bus_recv_queues.items():
|
||||||
|
try:
|
||||||
|
recv_data = q.get_nowait()
|
||||||
|
try:
|
||||||
|
udp_send_queue.put_nowait(recv_data)
|
||||||
|
logger.debug(
|
||||||
|
f"上行数据转发到udp_send_queue|{protocol}|{recv_data['dev_name']}|{recv_data['raw_data'].hex()}"
|
||||||
|
f"UDP队列长度:{udp_send_queue.qsize()}"
|
||||||
|
)
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"UDP发送队列已满,丢弃 {protocol} 上行数据")
|
||||||
|
q.task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
time.sleep(1e-6)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"总线接收分发线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
# ========== 7. UDP发送线程(无优先级,保持原有逻辑) ==========
|
||||||
|
def udp_send_thread(udp_server): #2026/4/15停用,因为本函数智能输出一个UDP端口
|
||||||
|
"""UDP发送线程:发送上行数据到故障注入平台"""
|
||||||
|
logger.info("UDP发送线程(上行数据)启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
send_data = udp_send_queue.get(timeout=1)
|
||||||
|
dev_name = send_data["dev_name"]
|
||||||
|
raw_data = send_data["raw_data"]
|
||||||
|
|
||||||
|
udp_server.send_to_fault(raw_data, dev_name)
|
||||||
|
logger.debug(
|
||||||
|
f"UDP发送|{dev_name}|数据:{raw_data.hex()}|"
|
||||||
|
f"队列剩余:{udp_send_queue.qsize()}"
|
||||||
|
)
|
||||||
|
udp_send_queue.task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP发送线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
'''
|
||||||
|
# ========== 7. UDP发送线程(已修改:按设备send_port动态发送) ==========2026/4/15
|
||||||
|
def udp_send_thread(udp_server):
|
||||||
|
"""UDP发送线程:按设备配置的send_port发送到故障注入平台"""
|
||||||
|
logger.info("UDP发送线程(上行数据)启动")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
send_data = udp_send_queue.get(timeout=1)
|
||||||
|
dev_name = send_data["dev_name"]
|
||||||
|
raw_data = send_data["raw_data"]
|
||||||
|
protocol = send_data["protocol"]
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# 🔥 关键:从设备配置获取远端端口 send_port
|
||||||
|
# ======================
|
||||||
|
send_port = 9999 # 默认端口
|
||||||
|
if dev_name in base_config.DEVICE_CONFIG_DICT: ##引用全局变量时候有问题,这里需要修改
|
||||||
|
dev_cfg = base_config.DEVICE_CONFIG_DICT[dev_name]
|
||||||
|
send_port = dev_cfg.get("remote_port", 9999) # 读取CSV里的 remote_port
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# 🔥 发送到指定远端端口(修改这里)
|
||||||
|
# ======================
|
||||||
|
udp_server.send_to_fault(raw_data, dev_name, send_port)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"通过UDP向动力学或前端发送|{dev_name}|port:{send_port}|数据:{raw_data.hex()}" #防止刷屏,先注释
|
||||||
|
)
|
||||||
|
udp_send_queue.task_done()
|
||||||
|
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP发送线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
'''
|
||||||
|
# ========== 8. 线程管理(启动/停止) ==========
|
||||||
|
def start_all_threads(udp_server, bus_drivers: Dict[str, Any] = None):#2026/3/9停用
|
||||||
|
"""启动所有线程(按优先级顺序启动,非必须,仅为日志清晰)"""
|
||||||
|
threads = []
|
||||||
|
bus_drivers = bus_drivers or {}
|
||||||
|
|
||||||
|
# 按优先级从高到低启动总线发送线程(日志更清晰)
|
||||||
|
sorted_protos = sorted(PROTOCOL_PRIORITY.keys(), key=lambda x: PROTOCOL_PRIORITY[x])
|
||||||
|
|
||||||
|
# 1. UDP接收线程
|
||||||
|
udp_recv_t = threading.Thread(target=udp_recv_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_recv_t)
|
||||||
|
|
||||||
|
# 2. 总线发送线程(按优先级排序启动)
|
||||||
|
for protocol in sorted_protos:
|
||||||
|
driver = bus_drivers.get(protocol)
|
||||||
|
send_t = threading.Thread(target=bus_send_thread, args=(protocol, driver), daemon=True)
|
||||||
|
threads.append(send_t)
|
||||||
|
|
||||||
|
# 3. 总线接收线程(按优先级排序启动)
|
||||||
|
for protocol in sorted_protos:
|
||||||
|
driver = bus_drivers.get(protocol)
|
||||||
|
recv_t = threading.Thread(target=bus_recv_thread, args=(protocol, driver), daemon=True)
|
||||||
|
threads.append(recv_t)
|
||||||
|
|
||||||
|
# 4. 总线接收分发线程
|
||||||
|
recv_dispatch_t = threading.Thread(target=bus_recv_dispatch_thread, daemon=True)
|
||||||
|
threads.append(recv_dispatch_t)
|
||||||
|
|
||||||
|
# 5. UDP发送线程
|
||||||
|
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_send_t)
|
||||||
|
|
||||||
|
# 启动所有线程
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
logger.info(f"线程 {t.name} 启动成功")
|
||||||
|
return threads
|
||||||
|
'''
|
||||||
|
def stop_all_threads():
|
||||||
|
"""优雅停止所有线程"""
|
||||||
|
logger.info("开始停止所有线程,等待队列处理完成...")
|
||||||
|
# 等待发送队列(按优先级从高到低)
|
||||||
|
sorted_protos = sorted(PROTOCOL_PRIORITY.keys(), key=lambda x: PROTOCOL_PRIORITY[x])
|
||||||
|
for protocol in sorted_protos:
|
||||||
|
bus_send_queues[protocol].join()
|
||||||
|
# 等待接收队列
|
||||||
|
for protocol in sorted_protos:
|
||||||
|
bus_recv_queues[protocol].join()
|
||||||
|
# 等待UDP发送队列
|
||||||
|
udp_send_queue.join()
|
||||||
|
logger.info("所有队列处理完成,线程已停止")
|
||||||
|
|
||||||
|
# ========== 扩展函数:动态调整优先级(可选) ==========
|
||||||
|
def update_protocol_priority(protocol: str, new_priority: int):
|
||||||
|
"""
|
||||||
|
动态调整协议优先级(运行时生效)
|
||||||
|
:param protocol: 协议名称(如"1553B")
|
||||||
|
:param new_priority: 新优先级数值(越小越高)
|
||||||
|
"""
|
||||||
|
if protocol in PROTOCOL_PRIORITY:
|
||||||
|
old_priority = PROTOCOL_PRIORITY[protocol]
|
||||||
|
PROTOCOL_PRIORITY[protocol] = new_priority
|
||||||
|
logger.info(f"协议 {protocol} 优先级调整:{old_priority} → {new_priority}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"未知协议 {protocol},无法调整优先级")
|
||||||
0
midware/core/data_forward.py
Normal file
0
midware/core/data_forward.py
Normal file
97
midware/core/error_check.py
Normal file
97
midware/core/error_check.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import crcmod
|
||||||
|
from loguru import logger
|
||||||
|
import os
|
||||||
|
|
||||||
|
#初始化校验和算法,适配不同总线协议(通用 CRC16 为主,兼容自定义校验)
|
||||||
|
#预定义各总线校验算法,可根据需求扩展
|
||||||
|
#CRC16_UART = crcmod.predefined.Crc('crc16_modbus')
|
||||||
|
CRC16_UART = crcmod.predefined.Crc('crc16')
|
||||||
|
#CRC16_CAN = crcmod.predefined.Crc('crc16/ccitt')
|
||||||
|
CRC16_CAN = crcmod.predefined.Crc('crc16')
|
||||||
|
#CRC16_1553B = crcmod.predefined.Crc('crc16_x25')
|
||||||
|
CRC16_1553B = crcmod.predefined.Crc('crc16')
|
||||||
|
|
||||||
|
|
||||||
|
#确保日志目录存在
|
||||||
|
if not os.path.exists("logs"):os.makedirs("logs")
|
||||||
|
def _calculate_checksum (data: bytes, protocol: str) -> bytes:
|
||||||
|
"""私有方法:根据协议类型计算校验和
|
||||||
|
:param data: 原始二进制数据
|
||||||
|
:param protocol: 总线协议(UART/CAN/1553B/AD/OC)
|
||||||
|
:return: 校验和字节流(2 字节,大端)
|
||||||
|
"""
|
||||||
|
if not isinstance (data, bytes) or len (data) == 0:
|
||||||
|
return b'\x00\x00'
|
||||||
|
crc = None
|
||||||
|
if protocol == "UART":
|
||||||
|
crc = CRC16_UART
|
||||||
|
elif protocol == "CAN":
|
||||||
|
crc = CRC16_CAN
|
||||||
|
elif protocol == "1553B":
|
||||||
|
crc = CRC16_1553B
|
||||||
|
elif protocol in ["AD", "OC"]:
|
||||||
|
#AD/OC 采用简单累加和校验(低字节),适配星务平台常用规则
|
||||||
|
sum_val = sum(byte for byte in data) & 0xFF
|
||||||
|
return bytes([sum_val])
|
||||||
|
else:
|
||||||
|
#未知协议默认使用 CRC16_MODBUS
|
||||||
|
crc = CRC16_UART
|
||||||
|
#重置 CRC 计算器并计算
|
||||||
|
#crc.reset()
|
||||||
|
crc.update(data)
|
||||||
|
|
||||||
|
#返回 2 字节大端校验和
|
||||||
|
return crc.crcValue.to_bytes(2, byteorder='big')
|
||||||
|
|
||||||
|
def check_checksum (data: bytes, protocol: str, device_name: str) -> bool:
|
||||||
|
"""核心校验和检测逻辑:分离数据与校验位,对比计算值与传入值规则:数据末尾携带校验位,不同协议校验位长度不同(UART/CAN/1553B 为 2 字节,AD/OC 为 1 字节)
|
||||||
|
:param data: 包含校验位的原始二进制数据
|
||||||
|
:param protocol: 总线协议(UART/CAN/1553B/AD/OC)
|
||||||
|
:param device_name: 外设名称(用于日志定位)
|
||||||
|
:return: 校验通过返回 True,失败返回 False 并记录错误日志
|
||||||
|
"""
|
||||||
|
#基础参数校验
|
||||||
|
if not isinstance (data, bytes) or len (data) == 0:
|
||||||
|
logger.error (f"【{device_name}-{protocol}】校验和检测失败:输入数据为空或非二进制格式")
|
||||||
|
return False
|
||||||
|
if protocol not in ["UART", "CAN", "1553B", "AD", "OC"]:
|
||||||
|
logger.error (f"【{device_name}-{protocol}】校验和检测失败:不支持的协议类型")
|
||||||
|
return False
|
||||||
|
#定义各协议校验位长度
|
||||||
|
checksum_len = 2 if protocol in ["UART", "CAN", "1553B"] else 1
|
||||||
|
#数据长度需大于校验位长度,否则无效
|
||||||
|
if len (data) <= checksum_len:
|
||||||
|
logger.error (f"【{device_name}-{protocol}】校验和检测失败:数据长度 {len (data)} 字节,小于校验位长度 {checksum_len} 字节")
|
||||||
|
return False
|
||||||
|
#分离原始数据和携带的校验位
|
||||||
|
raw_data = data[:-checksum_len]
|
||||||
|
received_checksum = data[-checksum_len:]
|
||||||
|
#计算原始数据的校验和
|
||||||
|
calculated_checksum = _calculate_checksum(raw_data, protocol)
|
||||||
|
#对比校验和
|
||||||
|
if received_checksum == calculated_checksum:
|
||||||
|
logger.debug (f"【{device_name}-{protocol}】校验和检测通过:接收校验位 {received_checksum.hex ()},计算校验位 {calculated_checksum.hex ()}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
#校验失败,记录详细错误日志(含设备、协议、数据、校验位对比)
|
||||||
|
error_msg = (f"【{device_name}-{protocol}】校验和检测失败 |"f"原始数据长度:{len (raw_data)} 字节 |"f"接收校验位:{received_checksum.hex ()} |"f"计算校验位:{calculated_checksum.hex ()} |"f"原始数据(前 16 字节):{raw_data [:16].hex () if len (raw_data)>=16 else raw_data.hex ()}")
|
||||||
|
logger.error (error_msg)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def verify_frame_integrity (data: bytes, protocol: str, device_name: str) -> bool:
|
||||||
|
"""扩展方法:帧完整性检测(可选),结合校验和 + 协议帧长度规则用于 CAN(单帧≤11 字节)、1553B 等有帧长限制的协议
|
||||||
|
:param data: 包含校验位的原始二进制数据
|
||||||
|
:return: 完整性通过返回 True,否则 False"""
|
||||||
|
#先执行校验和检测
|
||||||
|
if not check_checksum(data, protocol, device_name):
|
||||||
|
return False
|
||||||
|
#各协议帧长度规则校验
|
||||||
|
frame_rules = {"CAN": lambda d: len (d) - 2 <= 11, # CAN 单帧≤11 字节(扣除 2 字节校验位)"1553B": lambda d: len (d) % 2 == 0, # 1553B 数据为 2 字节整数倍(军用总线规范)"UART": lambda d: True, # UART 不定长,无帧长限制"AD": lambda d: True, # AD 无帧长限制"OC": lambda d: True # OC 无帧长限制
|
||||||
|
}
|
||||||
|
if protocol in frame_rules:
|
||||||
|
if not frame_rulesprotocol:
|
||||||
|
error_msg = f"【{device_name}-{protocol}】帧完整性检测失败:数据长度不符合协议规范,数据总长度 {len (data)} 字节"
|
||||||
|
logger.error (error_msg)
|
||||||
|
return False
|
||||||
|
logger.debug (f"【{device_name}-{protocol}】帧完整性检测通过")
|
||||||
|
return True
|
||||||
Binary file not shown.
203
midware/core/history/bus_udp_decoupler_20260306.py
Normal file
203
midware/core/history/bus_udp_decoupler_20260306.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Created on Tan Mingyan 2026/3/6
|
||||||
|
|
||||||
|
四、关键设计亮点(解耦 + 队列的核心价值)
|
||||||
|
彻底解耦:
|
||||||
|
UDP 收发 ≠ 总线处理:UDP 线程只做「数据搬运」,总线线程只做「业务处理」,修改总线逻辑无需改动 UDP 代码;
|
||||||
|
异常隔离:某一线程(如 CAN 处理)崩溃,不影响 UDP 收发和其他总线线程运行。
|
||||||
|
队列缓冲区的核心作用:
|
||||||
|
削峰填谷:UDP 突发大量数据时,队列暂存,总线线程按能力消费,避免数据丢失;
|
||||||
|
异步通信:线程间通过队列通信,无直接调用,消除阻塞依赖;
|
||||||
|
可监控:通过 qsize() 监控队列长度,及时发现「生产 > 消费」的瓶颈(如队列持续满则需优化总线处理速度)。
|
||||||
|
工业级容错:
|
||||||
|
队列满时丢弃数据 + 告警,避免内存溢出;
|
||||||
|
线程异常时休眠重试,避免 CPU 100%;
|
||||||
|
守护线程(daemon=True):主线程退出时自动终止子线程,避免僵尸线程;
|
||||||
|
优雅退出:join() 等待队列处理完成,避免强制退出导致数据丢失。
|
||||||
|
五、扩展建议(根据业务需求调整)
|
||||||
|
队列持久化:若需断电不丢数据,可将队列数据写入本地文件 / Redis;
|
||||||
|
优先级队列:对关键设备(如 1553B)使用 queue.PriorityQueue,优先处理高优先级数据;
|
||||||
|
流量控制:监控队列长度,超过阈值时暂停 UDP 接收(或降低接收频率);
|
||||||
|
线程池优化:若总线协议过多,改用 concurrent.futures.ThreadPoolExecutor 管理线程,避免线程数量过多。
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
总线-UDP解耦核心模块:队列+多线程
|
||||||
|
"""
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
from loguru import logger
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
# ========== 1. 全局队列定义(按协议分类) ==========
|
||||||
|
# 队列最大长度:避免内存溢出,根据业务调整(如1000)
|
||||||
|
MAX_QUEUE_SIZE = 1000
|
||||||
|
|
||||||
|
# 协议分发队列(UDP接收→协议分流)
|
||||||
|
protocol_dispatch_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
|
||||||
|
# 各总线处理队列
|
||||||
|
bus_queues = {
|
||||||
|
"UART": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"CAN": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"1553B": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"AD": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"OC": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"网络": queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
# UDP发送队列(总线回传→UDP发送)
|
||||||
|
udp_send_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
|
||||||
|
# ========== 2. UDP接收线程(仅负责收数据,不处理) ==========
|
||||||
|
def udp_receive_thread(udp_server):
|
||||||
|
"""UDP接收线程:独立运行,仅收数据放入分发队列"""
|
||||||
|
logger.info("UDP接收线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
recv_data = udp_server.recv_multi_udp_hex()
|
||||||
|
if recv_data:
|
||||||
|
for data in recv_data:
|
||||||
|
# 非阻塞放入队列,避免线程阻塞
|
||||||
|
try:
|
||||||
|
protocol_dispatch_queue.put_nowait(data)
|
||||||
|
logger.debug(f"UDP接收:{data['dev_name']} 数据放入分发队列,队列当前长度:{protocol_dispatch_queue.qsize()}")
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"协议分发队列已满,丢弃 {data['dev_name']} 数据")
|
||||||
|
time.sleep(1e-6)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1) # 异常时休眠,避免CPU飙高
|
||||||
|
|
||||||
|
# ========== 3. 协议分发线程(仅负责分流,不处理业务) ==========
|
||||||
|
def protocol_dispatch_thread():
|
||||||
|
"""协议分发线程:从分发队列取数据,按protocol分流到对应总线队列"""
|
||||||
|
logger.info("协议分发线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 阻塞取数据(队列为空时等待,无CPU消耗)
|
||||||
|
data = protocol_dispatch_queue.get(timeout=1)
|
||||||
|
protocol = data.get("protocol", "").strip().upper()
|
||||||
|
|
||||||
|
# 分流到对应总线队列
|
||||||
|
if protocol in bus_queues:
|
||||||
|
try:
|
||||||
|
bus_queues[protocol].put_nowait(data)
|
||||||
|
logger.debug(f"分发:{data['dev_name']} 数据到 {protocol} 队列,队列长度:{bus_queues[protocol].qsize()}")
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"{protocol} 队列已满,丢弃 {data['dev_name']} 数据")
|
||||||
|
else:
|
||||||
|
logger.warning(f"未知协议 {protocol},丢弃 {data['dev_name']} 数据")
|
||||||
|
|
||||||
|
# 标记任务完成(队列join时需要)
|
||||||
|
protocol_dispatch_queue.task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue # 队列为空,继续循环
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"协议分发线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 4. 总线处理线程(以UART为例,其他协议同理) ==========
|
||||||
|
def bus_process_thread(protocol: str, udp_server):
|
||||||
|
"""通用总线处理线程:从对应队列取数据,处理后放入UDP发送队列"""
|
||||||
|
logger.info(f"{protocol} 总线处理线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 阻塞取数据
|
||||||
|
data = bus_queues[protocol].get(timeout=1)
|
||||||
|
dev_name = data["dev_name"]
|
||||||
|
raw_data = data["raw_data"]
|
||||||
|
|
||||||
|
# ========== 核心:总线业务处理(解耦后仅在此处修改) ==========
|
||||||
|
logger.info(f"{protocol} 处理:{dev_name} 原始数据:{raw_data.hex()}")
|
||||||
|
# 1. 总线数据解析(如UART波特率解析、CAN帧解析等)
|
||||||
|
processed_data = raw_data # 示例:实际需替换为业务逻辑
|
||||||
|
# 2. 总线数据发送(如写入UART串口、发送CAN帧等)
|
||||||
|
# bus_driver.send(protocol, processed_data) # 总线驱动调用
|
||||||
|
|
||||||
|
# ========== 处理完成后,回传数据到UDP发送队列 ==========
|
||||||
|
try:
|
||||||
|
udp_send_queue.put_nowait({
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"data": processed_data,
|
||||||
|
"protocol": protocol
|
||||||
|
})
|
||||||
|
logger.debug(f"{protocol} 处理完成,{dev_name} 回传数据放入UDP发送队列")
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"UDP发送队列已满,丢弃 {dev_name} 回传数据")
|
||||||
|
|
||||||
|
# 标记任务完成
|
||||||
|
bus_queues[protocol].task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 总线线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 5. UDP发送线程(仅负责发数据,不处理) ==========
|
||||||
|
def udp_send_thread(udp_server):
|
||||||
|
"""UDP发送线程:从发送队列取数据,发送到故障注入平台"""
|
||||||
|
logger.info("UDP发送线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 阻塞取数据
|
||||||
|
send_data = udp_send_queue.get(timeout=1)
|
||||||
|
dev_name = send_data["dev_name"]
|
||||||
|
data = send_data["data"]
|
||||||
|
|
||||||
|
# 调用UDP服务发送
|
||||||
|
udp_server.send_to_fault(data, dev_name)
|
||||||
|
logger.debug(f"UDP发送:{dev_name} 数据到故障注入平台,队列剩余:{udp_send_queue.qsize()}")
|
||||||
|
|
||||||
|
# 标记任务完成
|
||||||
|
udp_send_queue.task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP发送线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 6. 线程启动/停止管理(集成到main.py) ==========
|
||||||
|
'''
|
||||||
|
def start_all_threads(udp_server):
|
||||||
|
"""启动所有解耦线程"""
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
# 1. UDP接收线程
|
||||||
|
udp_recv_t = threading.Thread(target=udp_receive_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_recv_t)
|
||||||
|
|
||||||
|
# 2. 协议分发线程
|
||||||
|
dispatch_t = threading.Thread(target=protocol_dispatch_thread, daemon=True)
|
||||||
|
threads.append(dispatch_t)
|
||||||
|
|
||||||
|
# 3. 各总线处理线程
|
||||||
|
for protocol in bus_queues.keys():
|
||||||
|
bus_t = threading.Thread(target=bus_process_thread, args=(protocol, udp_server), daemon=True)
|
||||||
|
threads.append(bus_t)
|
||||||
|
|
||||||
|
# 4. UDP发送线程
|
||||||
|
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_send_t)
|
||||||
|
|
||||||
|
# 启动所有线程
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
logger.info(f"线程 {t.name} 启动成功")
|
||||||
|
|
||||||
|
return threads
|
||||||
|
|
||||||
|
def stop_all_threads():
|
||||||
|
"""优雅停止所有线程(等待队列处理完成)"""
|
||||||
|
logger.info("开始停止所有线程,等待队列处理完成...")
|
||||||
|
|
||||||
|
# 等待队列所有任务完成
|
||||||
|
protocol_dispatch_queue.join()
|
||||||
|
for q in bus_queues.values():
|
||||||
|
q.join()
|
||||||
|
udp_send_queue.join()
|
||||||
|
|
||||||
|
logger.info("所有队列处理完成,线程已停止")
|
||||||
|
'''
|
||||||
234
midware/core/history/bus_udp_decoupler_20260307.py
Normal file
234
midware/core/history/bus_udp_decoupler_20260307.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Created on Tan Mingyan 2026/3/7
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
总线-UDP双向通信解耦模块(加入收发缓冲区分离)
|
||||||
|
"""
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
from loguru import logger
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
# ========== 1. 收发队列分离:为每个协议定义独立的收/发队列 ==========
|
||||||
|
MAX_QUEUE_SIZE = 1000 # 可按协议单独调整(如1553B设2000,UART设500)
|
||||||
|
|
||||||
|
# 总线接收队列(总线→中间件:上行数据)
|
||||||
|
bus_recv_queues = {
|
||||||
|
"UART": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"CAN": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"1553B": queue.Queue(maxsize=2000), # 1553B数据量大,单独调整
|
||||||
|
"AD": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"OC": queue.Queue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"网络": queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 总线发送队列(中间件→总线:下行数据)
|
||||||
|
bus_send_queues = {
|
||||||
|
"UART": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE), # 发送队列支持优先级
|
||||||
|
"CAN": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"1553B": queue.PriorityQueue(maxsize=2000),
|
||||||
|
"AD": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"OC": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
|
||||||
|
"网络": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
# UDP发送队列(中间件→UDP:上行数据,总线接收后转发)
|
||||||
|
udp_send_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
# UDP接收队列(UDP→中间件:下行数据,转发到总线发送队列)
|
||||||
|
udp_recv_dispatch_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
|
||||||
|
|
||||||
|
# ========== 2. UDP接收线程:仅处理下行指令(转发到总线发送队列) ==========
|
||||||
|
def udp_recv_thread(udp_server):
|
||||||
|
"""UDP接收线程:接收故障注入平台的下行指令,分发到总线发送队列"""
|
||||||
|
logger.info("UDP接收线程(下行指令)启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
recv_data = udp_server.recv_multi_udp_hex()
|
||||||
|
if recv_data:
|
||||||
|
for data in recv_data:
|
||||||
|
protocol = data.get("protocol", "").strip().upper()
|
||||||
|
dev_name = data.get("dev_name", "")
|
||||||
|
raw_data = data.get("raw_data", b"")
|
||||||
|
priority = data.get("priority", 5) # 默认优先级5(1最高,10最低)
|
||||||
|
|
||||||
|
if protocol not in bus_send_queues:
|
||||||
|
logger.warning(f"未知协议 {protocol},丢弃下行指令:{dev_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 放入总线发送队列(带优先级)
|
||||||
|
try:
|
||||||
|
# PriorityQueue格式:(优先级, 数据)
|
||||||
|
bus_send_queues[protocol].put_nowait((priority, {
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": raw_data,
|
||||||
|
"protocol": protocol
|
||||||
|
}))
|
||||||
|
logger.debug(f"下行指令:{dev_name} ({protocol}) 放入发送队列,优先级:{priority}")
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"{protocol} 发送队列已满,丢弃 {dev_name} 下行指令")
|
||||||
|
time.sleep(1e-6)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 3. 总线发送线程:处理下行指令(中间件→总线) ==========
|
||||||
|
def bus_send_thread(protocol: str, bus_driver: Optional[Any] = None):
|
||||||
|
"""总线发送线程:从发送队列取下行指令,发送到总线"""
|
||||||
|
logger.info(f"{protocol} 总线发送线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# PriorityQueue阻塞取数据(优先级高的先取)
|
||||||
|
priority, send_data = bus_send_queues[protocol].get(timeout=1)
|
||||||
|
dev_name = send_data["dev_name"]
|
||||||
|
raw_data = send_data["raw_data"]
|
||||||
|
|
||||||
|
# 总线发送逻辑(适配双向通信:调用总线驱动发送指令)
|
||||||
|
logger.info(f"{protocol} 发送:{dev_name} | 优先级:{priority} | 数据:{raw_data.hex()}")
|
||||||
|
# 实际业务:调用总线驱动发送(如CAN发送帧、UART写串口)
|
||||||
|
# if bus_driver:
|
||||||
|
# bus_driver.send(raw_data)
|
||||||
|
|
||||||
|
# 标记任务完成
|
||||||
|
bus_send_queues[protocol].task_done()
|
||||||
|
|
||||||
|
# 在bus_send_thread中添加超时检查
|
||||||
|
if time.time() - send_data.get("send_time", 0) > 5: # 5秒超时
|
||||||
|
logger.error(f"{protocol} 发送超时:{dev_name}")
|
||||||
|
bus_send_queues[protocol].task_done()
|
||||||
|
continue
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 发送线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 4. 总线接收线程:处理上行数据(总线→中间件) ==========
|
||||||
|
def bus_recv_thread(protocol: str, bus_driver: Optional[Any] = None):
|
||||||
|
"""总线接收线程:从总线接收上行数据,放入接收队列"""
|
||||||
|
logger.info(f"{protocol} 总线接收线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 模拟总线接收(实际需替换为总线驱动的接收接口)
|
||||||
|
# raw_data = bus_driver.recv() # 从总线读取上行数据
|
||||||
|
# 此处为示例,实际需对接真实总线驱动
|
||||||
|
raw_data = b"" # 占位:替换为总线接收逻辑
|
||||||
|
|
||||||
|
if raw_data:
|
||||||
|
dev_name = f"{protocol}_DEV" # 实际从总线数据解析设备名
|
||||||
|
# 放入总线接收队列
|
||||||
|
try:
|
||||||
|
bus_recv_queues[protocol].put_nowait({
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": raw_data,
|
||||||
|
"protocol": protocol,
|
||||||
|
"recv_time": time.time() # 记录接收时间,便于时序分析
|
||||||
|
})
|
||||||
|
logger.debug(f"{protocol} 接收:{dev_name} 上行数据,队列长度:{bus_recv_queues[protocol].qsize()}")
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"{protocol} 接收队列已满,丢弃上行数据")
|
||||||
|
time.sleep(1e-6) # 匹配总线接收速率
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 5. 总线接收分发线程:汇总上行数据→UDP发送 ==========
|
||||||
|
def bus_recv_dispatch_thread():
|
||||||
|
"""总线接收分发线程:汇总各协议接收队列数据,转发到UDP发送队列"""
|
||||||
|
logger.info("总线接收分发线程启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 遍历所有总线接收队列,非阻塞取数据(避免单队列阻塞)
|
||||||
|
for protocol, q in bus_recv_queues.items():
|
||||||
|
try:
|
||||||
|
recv_data = q.get_nowait()
|
||||||
|
# 转发到UDP发送队列(发送到故障注入平台)
|
||||||
|
try:
|
||||||
|
udp_send_queue.put_nowait(recv_data)
|
||||||
|
logger.debug(f"{protocol} 上行数据:{recv_data['dev_name']} 转发到UDP发送队列")
|
||||||
|
except queue.Full:
|
||||||
|
logger.warning(f"UDP发送队列已满,丢弃 {protocol} 上行数据")
|
||||||
|
q.task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
time.sleep(1e-6)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"总线接收分发线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ========== 6. UDP发送线程:处理上行数据(中间件→UDP) ==========
|
||||||
|
def udp_send_thread(udp_server):
|
||||||
|
"""UDP发送线程:从UDP发送队列取上行数据,发送到故障注入平台"""
|
||||||
|
logger.info("UDP发送线程(上行数据)启动")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
send_data = udp_send_queue.get(timeout=1)
|
||||||
|
dev_name = send_data["dev_name"]
|
||||||
|
raw_data = send_data["raw_data"]
|
||||||
|
|
||||||
|
# 调用UDP服务发送到故障注入平台
|
||||||
|
udp_server.send_to_fault(raw_data, dev_name)
|
||||||
|
logger.debug(f"UDP发送:{dev_name} 上行数据,队列剩余:{udp_send_queue.qsize()}")
|
||||||
|
udp_send_queue.task_done()
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP发送线程异常:{str(e)}", exc_info=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
'''
|
||||||
|
# ========== 7. 线程管理:启动/停止所有收发线程 ==========
|
||||||
|
def start_all_threads(udp_server, bus_drivers: Dict[str, Any] = None):
|
||||||
|
"""
|
||||||
|
启动所有双向通信线程
|
||||||
|
:param udp_server: UDP服务实例
|
||||||
|
:param bus_drivers: 总线驱动字典,格式:{"UART": uart_driver, "CAN": can_driver, ...}
|
||||||
|
"""
|
||||||
|
threads = []
|
||||||
|
bus_drivers = bus_drivers or {} # 无驱动时传空字典
|
||||||
|
|
||||||
|
# 1. UDP接收线程(下行指令)
|
||||||
|
udp_recv_t = threading.Thread(target=udp_recv_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_recv_t)
|
||||||
|
|
||||||
|
# 2. 总线发送线程(每个协议一个)
|
||||||
|
for protocol in bus_send_queues.keys():
|
||||||
|
driver = bus_drivers.get(protocol)
|
||||||
|
send_t = threading.Thread(target=bus_send_thread, args=(protocol, driver), daemon=True)
|
||||||
|
threads.append(send_t)
|
||||||
|
|
||||||
|
# 3. 总线接收线程(每个协议一个)
|
||||||
|
for protocol in bus_recv_queues.keys():
|
||||||
|
driver = bus_drivers.get(protocol)
|
||||||
|
recv_t = threading.Thread(target=bus_recv_thread, args=(protocol, driver), daemon=True)
|
||||||
|
threads.append(recv_t)
|
||||||
|
|
||||||
|
# 4. 总线接收分发线程
|
||||||
|
recv_dispatch_t = threading.Thread(target=bus_recv_dispatch_thread, daemon=True)
|
||||||
|
threads.append(recv_dispatch_t)
|
||||||
|
|
||||||
|
# 5. UDP发送线程(上行数据)
|
||||||
|
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
|
||||||
|
threads.append(udp_send_t)
|
||||||
|
|
||||||
|
# 启动所有线程
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
logger.info(f"线程 {t.name} 启动成功")
|
||||||
|
return threads
|
||||||
|
|
||||||
|
def stop_all_threads():
|
||||||
|
"""优雅停止所有线程,等待队列处理完成"""
|
||||||
|
logger.info("开始停止所有线程,等待队列处理完成...")
|
||||||
|
# 等待发送队列处理完成
|
||||||
|
for q in bus_send_queues.values():
|
||||||
|
q.join()
|
||||||
|
# 等待接收队列处理完成
|
||||||
|
for q in bus_recv_queues.values():
|
||||||
|
q.join()
|
||||||
|
# 等待UDP发送队列处理完成
|
||||||
|
udp_send_queue.join()
|
||||||
|
logger.info("所有队列处理完成,线程已停止")
|
||||||
|
|
||||||
|
'''
|
||||||
37
midware/drivers/__init__.py
Normal file
37
midware/drivers/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# midware/drivers/__init__.py
|
||||||
|
"""
|
||||||
|
总线驱动模块:统一管理所有总线驱动的导入和初始化
|
||||||
|
"""
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from .bus_1553b import Bus1553BDriver
|
||||||
|
from .bus_can import BusCANDriver
|
||||||
|
from .bus_uart import BusUARTDriver
|
||||||
|
from .bus_ad import BusADDriver
|
||||||
|
from .bus_oc import BusOCDriver
|
||||||
|
from .bus_network import BusNetworkDriver
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# 驱动映射表:协议名称 → 驱动类(与bus_udp_decoupler.py中的协议名对齐)
|
||||||
|
DRIVER_MAPPING = {
|
||||||
|
"1553B": Bus1553BDriver,
|
||||||
|
"CAN": BusCANDriver,
|
||||||
|
"UART": BusUARTDriver,
|
||||||
|
"AD": BusADDriver,
|
||||||
|
"OC": BusOCDriver,
|
||||||
|
"网络": BusNetworkDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_bus_driver(protocol: str, config: dict = None) -> BaseBusDriver:
|
||||||
|
"""
|
||||||
|
工厂函数:根据协议名称创建驱动实例
|
||||||
|
:param protocol: 协议名称(如"1553B")
|
||||||
|
:param config: 驱动配置
|
||||||
|
:return: 驱动实例
|
||||||
|
"""
|
||||||
|
if protocol not in DRIVER_MAPPING:
|
||||||
|
raise ValueError(f"未知协议 {protocol},无对应驱动")
|
||||||
|
|
||||||
|
driver_class = DRIVER_MAPPING[protocol]
|
||||||
|
driver = driver_class(config=config)
|
||||||
|
logger.info(f"创建 {protocol} 驱动实例成功:{driver.__class__.__name__}")
|
||||||
|
return driver
|
||||||
BIN
midware/drivers/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/base_driver.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/base_driver.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_1553b.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_1553b.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_ad.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_ad.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_can.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_can.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_network.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_network.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_oc.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_oc.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_uart.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_uart.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/renode_agent.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/renode_agent.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/tcp_uart_agent.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/tcp_uart_agent.cpython-311.pyc
Normal file
Binary file not shown.
78
midware/drivers/base_driver.py
Normal file
78
midware/drivers/base_driver.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# midware/drivers/base_driver.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
驱动基类(base_driver.py)—— 定义标准接口(核心)
|
||||||
|
所有总线驱动继承该基类,保证接口统一,解耦 bus_udp_decoupler.py 与具体驱动实现:
|
||||||
|
"""
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
class BaseBusDriver(ABC):
|
||||||
|
"""
|
||||||
|
总线驱动基类:定义所有总线必须实现的标准接口
|
||||||
|
遵循「开闭原则」:新增总线只需继承该类实现接口,无需修改现有代码
|
||||||
|
"""
|
||||||
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||||
|
"""
|
||||||
|
初始化驱动
|
||||||
|
:param config: 驱动配置(如波特率、端口、IP等)
|
||||||
|
"""
|
||||||
|
self.config = config or {}
|
||||||
|
self.is_connected = False # 驱动连接状态
|
||||||
|
self._init_config() # 初始化配置
|
||||||
|
|
||||||
|
def _init_config(self):
|
||||||
|
"""初始化默认配置(子类可重写)"""
|
||||||
|
logger.info(f"初始化 {self.__class__.__name__} 默认配置")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def connect(self) -> bool:
|
||||||
|
"""
|
||||||
|
连接总线(如打开串口、建立CAN通道、连接1553B板卡)
|
||||||
|
:return: 连接成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
"""
|
||||||
|
断开总线连接
|
||||||
|
:return: 断开成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
"""
|
||||||
|
发送数据到总线
|
||||||
|
:param data: 待发送的原始字节数据
|
||||||
|
:param kwargs: 扩展参数(如优先级、地址、帧ID等)
|
||||||
|
:return: 发送成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
"""
|
||||||
|
从总线接收数据
|
||||||
|
:param timeout: 接收超时时间(秒)
|
||||||
|
:param kwargs: 扩展参数(如过滤条件、地址等)
|
||||||
|
:return: 接收到的字节数据,超时/失败返回None
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_status(self) -> bool:
|
||||||
|
"""
|
||||||
|
检查驱动状态(默认实现,子类可重写)
|
||||||
|
:return: 驱动正常返回True,异常返回False
|
||||||
|
"""
|
||||||
|
return self.is_connected
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""析构函数:自动断开连接"""
|
||||||
|
if self.is_connected:
|
||||||
|
self.disconnect()
|
||||||
|
logger.info(f"{self.__class__.__name__} 自动断开连接")
|
||||||
100
midware/drivers/bus_1553b.py
Normal file
100
midware/drivers/bus_1553b.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
|
||||||
|
2. 具体驱动实现(以 1553B 为例,bus_1553b.py)
|
||||||
|
其他总线(CAN/UART/AD 等)按相同逻辑实现,仅需重写基类接口:
|
||||||
|
"""
|
||||||
|
# midware/drivers/bus_1553b.py
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
|
||||||
|
class Bus1553BDriver(BaseBusDriver):
|
||||||
|
"""1553B总线驱动实现(适配实际1553B板卡/仿真器)"""
|
||||||
|
def _init_config(self):
|
||||||
|
"""初始化1553B默认配置"""
|
||||||
|
# 默认配置(可从yaml文件加载)
|
||||||
|
self.config.setdefault("board_id", 0) # 板卡ID
|
||||||
|
self.config.setdefault("bc_address", 0x01) # BC地址
|
||||||
|
self.config.setdefault("rt_address", 0x02) # RT地址
|
||||||
|
self.config.setdefault("baudrate", 1000000) # 波特率1Mbps
|
||||||
|
logger.info(f"1553B驱动配置初始化完成:{self.config}")
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
"""连接1553B板卡/仿真器"""
|
||||||
|
try:
|
||||||
|
# 实际逻辑:调用1553B板卡SDK的连接接口
|
||||||
|
# 示例:self.board = SDK_1553B.connect(board_id=self.config["board_id"])
|
||||||
|
time.sleep(0.1) # 模拟连接耗时
|
||||||
|
self.is_connected = True
|
||||||
|
logger.info("1553B总线连接成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"1553B总线连接失败:{str(e)}")
|
||||||
|
self.is_connected = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
"""断开1553B连接"""
|
||||||
|
try:
|
||||||
|
# 实际逻辑:调用SDK的断开接口
|
||||||
|
# self.board.disconnect()
|
||||||
|
self.is_connected = False
|
||||||
|
logger.info("1553B总线断开成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"1553B总线断开失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
"""发送1553B数据帧"""
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("1553B驱动未连接,发送失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 扩展参数:优先级、子地址等
|
||||||
|
priority = kwargs.get("priority", 1) # 1553B最高优先级
|
||||||
|
sub_address = kwargs.get("sub_address", 0x00)
|
||||||
|
|
||||||
|
# 实际逻辑:调用SDK发送1553B帧
|
||||||
|
logger.info(
|
||||||
|
f"1553B发送数据|优先级:{priority}|子地址:{sub_address}|"
|
||||||
|
f"数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
)
|
||||||
|
# self.board.send_frame(
|
||||||
|
# bc_addr=self.config["bc_address"],
|
||||||
|
# rt_addr=self.config["rt_address"],
|
||||||
|
# sub_addr=sub_address,
|
||||||
|
# data=data,
|
||||||
|
# priority=priority
|
||||||
|
# )
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"1553B数据发送失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
"""接收1553B数据帧"""
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("1553B驱动未连接,接收失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 实际逻辑:调用SDK接收1553B帧
|
||||||
|
# frame = self.board.recv_frame(timeout=timeout)
|
||||||
|
# if frame:
|
||||||
|
# return frame.data
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# 模拟接收(替换为真实逻辑)
|
||||||
|
time.sleep(0.001) # 模拟接收耗时
|
||||||
|
# mock_data = b"\x01\x02\x03\x04\x05" # 模拟1553B数据
|
||||||
|
# logger.debug(f"1553B接收数据:{mock_data.hex()}")
|
||||||
|
#return mock_data
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"1553B数据接收失败:{str(e)}")
|
||||||
|
return None
|
||||||
190
midware/drivers/bus_ad.py
Normal file
190
midware/drivers/bus_ad.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
|
||||||
|
AD 驱动示例(bus_ad.py)—— 补充参考
|
||||||
|
"""
|
||||||
|
# midware/drivers/bus_can.py
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
# 导入全局配置字典(需确保main.py已提前加载CSV配置)
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT, get_device_config
|
||||||
|
import midware.config.base_config as base_config
|
||||||
|
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from .renode_agent import renode
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
class BusADDriver(BaseBusDriver):
|
||||||
|
"""uart总线驱动实现(适配)"""
|
||||||
|
def _init_config(self):
|
||||||
|
self.config.setdefault("channel", 0) # CAN通道
|
||||||
|
self.config.setdefault("baudrate", 500000) # 500kbps
|
||||||
|
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
|
||||||
|
logger.info(f"UART驱动配置初始化完成:{self.config}")
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
try:
|
||||||
|
# 实际逻辑:打开CAN通道
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.is_connected = True
|
||||||
|
logger.info("CAN总线连接成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线连接失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.is_connected = False
|
||||||
|
logger.info("CAN总线断开成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线断开失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("CAN驱动未连接,发送失败")
|
||||||
|
return False
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
|
||||||
|
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
|
||||||
|
logger.info(
|
||||||
|
f"CAN发送数据|帧ID:0x{frame_id:X}|扩展帧:{is_extended}|"
|
||||||
|
f"数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
)
|
||||||
|
# 实际逻辑:调用AD驱动发送接口
|
||||||
|
# 获取优先级,设备参数,地址等参数
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN数据发送失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
'''
|
||||||
|
# ========== 核心步骤1:从kwargs提取设备名称 ==========
|
||||||
|
#dev_name = kwargs.get("dev_name")[0]
|
||||||
|
dev_name = kwargs.get("dev_name")#2026/4/10
|
||||||
|
if not dev_name:
|
||||||
|
logger.error("AD发送失败:kwargs中未传入dev_name(设备名称)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ========== 核心步骤2:从全局配置字典查找设备参数 ==========
|
||||||
|
# 检查全局配置是否加载
|
||||||
|
if not base_config.DEVICE_CONFIG_DICT:
|
||||||
|
logger.error("AD发送失败:全局配置DEVICE_CONFIG_DICT未加载")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 查找当前设备的配置
|
||||||
|
|
||||||
|
#device_config = base_config.DEVICE_CONFIG_DICT.get(dev_name)
|
||||||
|
device_config = get_device_config().get(dev_name)
|
||||||
|
if not device_config:
|
||||||
|
logger.error(f"AD发送失败:设备 {dev_name} 未在DEVICE_CONFIG_DICT中配置")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 提取关键配置项(兼容配置项缺失的情况)
|
||||||
|
try:
|
||||||
|
# 内存基地址(转为16进制整数,如"0x80000000" → 0x80000000)
|
||||||
|
# mem_base_addr = int(str(device_config.get("base_addr", "0x0")), 16)#mem_base_addr = int(device_config.get("base_addr", "0x0"), 16)#内存基地址
|
||||||
|
mem_base_addr = int(device_config.get("base_addr", 0))
|
||||||
|
# 内存偏移地址
|
||||||
|
#mem_offset = int(str(device_config.get("offset_addr", "0x0")), 16)#内存偏移地址
|
||||||
|
mem_offset = int(device_config.get("offset_addr", 0))
|
||||||
|
# 发送缓存区大小(字节)
|
||||||
|
send_buffer_size = int(str(device_config.get("send_cache", 1024)))#发送缓存区大小
|
||||||
|
# 接收缓存区大小(字节)
|
||||||
|
recv_buffer_size = int(device_config.get("recv_cache", 1024))#接收缓存区大小
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
logger.error(f"AD发送失败:{dev_name} 配置参数解析错误 → {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ========== 核心步骤3:使用配置项处理发送逻辑 ==========
|
||||||
|
# 1. 校验数据长度不超过发送缓存区大小
|
||||||
|
if len(data) > send_buffer_size:
|
||||||
|
logger.error(
|
||||||
|
f"AD发送失败:{dev_name} 数据长度 {len(data)} 超过发送缓存区大小 {send_buffer_size}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 2. 模拟AD指令发送(结合内存地址)
|
||||||
|
logger.info(
|
||||||
|
f"AD发送指令|设备:{dev_name}|"
|
||||||
|
f"内存基地址:0x{mem_base_addr:X}|偏移地址:0x{mem_offset:X}|"
|
||||||
|
f"发送缓存区:{send_buffer_size}B|接收缓存区:{recv_buffer_size}B|"
|
||||||
|
f"数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 实际业务逻辑:
|
||||||
|
# - 将数据写入指定内存地址(mem_base_addr + mem_offset)
|
||||||
|
# - 配置接收缓存区大小
|
||||||
|
# - 调用AD采集卡SDK发送指令
|
||||||
|
# ad_card.write_mem(mem_base_addr + mem_offset, data, send_buffer_size)
|
||||||
|
|
||||||
|
#AD0-AD6的寄存器写入策略
|
||||||
|
if(dev_name == "正电压采集热敏量采集"):
|
||||||
|
for i in range(4):
|
||||||
|
logger.info(f"AD发送指令:{i}")
|
||||||
|
for j in range(14):
|
||||||
|
logger.info(f"AD发送指令:{j}")
|
||||||
|
target_addr = mem_base_addr + mem_offset + i * 32 + j*8
|
||||||
|
canshu = data[2*(i*14+j)]*256 + data[2*(i*14+j)+1]# 2字节
|
||||||
|
sysbus_cmd = f"sysbus WriteDoubleWord {target_addr:#010x} {canshu:#006x}"
|
||||||
|
print(sysbus_cmd)
|
||||||
|
pass
|
||||||
|
|
||||||
|
## 外设名称+函数名+字符串参数 adc1 LoadRegistersFromHexString "55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA"'
|
||||||
|
ad_cmd = f'{dev_name} LoadRegistersFromHexString "{data.hex()}"'
|
||||||
|
print(ad_cmd)
|
||||||
|
logger.info(f"AD发送指令:{ad_cmd}")
|
||||||
|
response = renode.send_sync(ad_cmd,10)#2026/4/10
|
||||||
|
ad_cmd2 = f'{dev_name} ReadAllChannelsToString02'
|
||||||
|
logger.info(f"AD发送指令:{ad_cmd2}")
|
||||||
|
response = renode.send_sync(ad_cmd2,10)#2026/4/10
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("ADC驱动未连接,接收失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 模拟接收
|
||||||
|
#遍历所有AD外设,循环发送读取指令:例如 adc1 ReadAllChannelsToString02
|
||||||
|
#接收renode回复
|
||||||
|
time.sleep(0.51)
|
||||||
|
'''
|
||||||
|
mock_data = b"\x11\x22\x33\x44"
|
||||||
|
logger.debug(f"AD接收数据:{mock_data.hex()}")
|
||||||
|
|
||||||
|
#2026/4/10
|
||||||
|
dev_name = kwargs.get("dev_name")
|
||||||
|
if not dev_name:
|
||||||
|
logger.error("AD接收失败:kwargs中未传入dev_name(设备名称)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
cfg = DEVICE_CONFIG_DICT[dev_name]
|
||||||
|
base = int(cfg["内存基地址"], 16)
|
||||||
|
offset = int(cfg["内存偏移地址"], 16)
|
||||||
|
addr = base + offset
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# 调用 Renode 读取数据
|
||||||
|
# ======================
|
||||||
|
cmd = f"read 0x{addr:X}"
|
||||||
|
hex_str = renode.send_sync(cmd)
|
||||||
|
#return bytes.fromhex(hex_str.strip())
|
||||||
|
logger.info(f"AD接收数据:{mock_data.hex()}")
|
||||||
|
return mock_data
|
||||||
|
'''
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"AD数据接收失败:{str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
133
midware/drivers/bus_can.py
Normal file
133
midware/drivers/bus_can.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
|
||||||
|
CAN 驱动示例(bus_can.py)—— 补充参考
|
||||||
|
"""
|
||||||
|
# midware/drivers/bus_can.py
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
|
||||||
|
from midware.config.base_config import get_device_config
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from .renode_agent import renode
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
class BusCANDriver(BaseBusDriver):
|
||||||
|
"""CAN总线驱动实现(适配CANoe/CANalyzer/USB-CAN)"""
|
||||||
|
def _init_config(self):
|
||||||
|
self.config.setdefault("channel", 0) # CAN通道
|
||||||
|
self.config.setdefault("baudrate", 500000) # 500kbps
|
||||||
|
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
|
||||||
|
logger.info(f"CAN驱动配置初始化完成:{self.config}")
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
try:
|
||||||
|
# 实际逻辑:打开CAN通道
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.is_connected = True
|
||||||
|
logger.info("CAN总线连接成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线连接失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.is_connected = False
|
||||||
|
logger.info("CAN总线断开成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线断开失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("CAN驱动未连接,发送失败")
|
||||||
|
return False
|
||||||
|
# ========== 核心步骤1:从kwargs提取设备名称 ==========
|
||||||
|
|
||||||
|
dev_name = kwargs.get("dev_name")#2026/4/10
|
||||||
|
if not dev_name:
|
||||||
|
logger.error("CAN发送失败:kwargs中未传入dev_name(设备名称)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
device_config = get_device_config().get(dev_name)
|
||||||
|
if not device_config:
|
||||||
|
logger.error(f"CAN发送失败:设备 {dev_name} 未在DEVICE_CONFIG_DICT中配置")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
|
||||||
|
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
|
||||||
|
logger.info(
|
||||||
|
f"CAN发送数据|帧ID:0x{frame_id:X}|扩展帧:{is_extended}|"
|
||||||
|
f"数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
)
|
||||||
|
# logger.info(
|
||||||
|
# #f"UART发送数据|帧ID:0x{frame_id:X}|扩展帧:{is_extended}|"
|
||||||
|
# f"CAN发送数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
# )
|
||||||
|
## 外设名称+函数名+字符串参数 uart24 WriteRXFIFODataString "0xEB9000C571000827C00000BC1003190000106C02AC020101FF5716"'
|
||||||
|
|
||||||
|
## 收到的CAN帧可能是多帧连在一起,需要拆分单帧保证虚拟平台能读取。
|
||||||
|
frame_len = 11
|
||||||
|
frame_list = []
|
||||||
|
total =len(data)
|
||||||
|
#步长11字节截取完整帧,不满足的丢弃(尾帧长度可以不满11,不丢弃)
|
||||||
|
end = 0
|
||||||
|
for start in range(0,total, frame_len):
|
||||||
|
'''
|
||||||
|
if end>total:
|
||||||
|
break
|
||||||
|
end = start + frame_len
|
||||||
|
frame_list.append(data[start:end])
|
||||||
|
sysbus_cmd = f'{dev_name} SendRxBufferDataString "0x{data[start:end].hex()}"'
|
||||||
|
#print(sysbus_cmd)
|
||||||
|
logger.info(f"CAN发送指令:{sysbus_cmd}")
|
||||||
|
response = renode.send_sync(sysbus_cmd)#2026/4/10
|
||||||
|
time.sleep(0.0001)
|
||||||
|
'''
|
||||||
|
#计算当前帧结束的位置
|
||||||
|
end = min(start + frame_len, total)
|
||||||
|
frame = data[start:end]
|
||||||
|
#尾帧长度不足11时,补0到11字节
|
||||||
|
if len(frame)<frame_len:
|
||||||
|
#补0
|
||||||
|
frame = frame.ljust(frame_len, b'\x00')
|
||||||
|
frame_list.append(frame)
|
||||||
|
sysbus_cmd = f'{dev_name} SendRxBufferDataString "0x{frame.hex()}"'
|
||||||
|
logger.info(f"CAN发送指令:{sysbus_cmd}")
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=2)#2026/4/10
|
||||||
|
# response = renode.enqueue_cmd(sysbus_cmd)#2026/6/4
|
||||||
|
time.sleep(0.0001)
|
||||||
|
|
||||||
|
|
||||||
|
#sysbus_cmd = f'{dev_name} SendRxBufferDataString "0x{data.hex()}"'
|
||||||
|
#print(sysbus_cmd)
|
||||||
|
#logger.info(f"CAN发送指令:{sysbus_cmd}")
|
||||||
|
#response = renode.send_sync(sysbus_cmd)#2026/4/10
|
||||||
|
|
||||||
|
# 实际逻辑:调用CAN驱动发送接口
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN数据发送失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("CAN驱动未连接,接收失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 模拟接收
|
||||||
|
time.sleep(0.001)
|
||||||
|
# mock_data = b"\x11\x22\x33\x44"
|
||||||
|
# logger.debug(f"CAN接收数据:{mock_data.hex()}")
|
||||||
|
# return mock_data
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN数据接收失败:{str(e)}")
|
||||||
|
return None
|
||||||
73
midware/drivers/bus_network.py
Normal file
73
midware/drivers/bus_network.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
|
||||||
|
network 驱动示例(bus_network .py)—— 补充参考
|
||||||
|
"""
|
||||||
|
# midware/drivers/bus_can.py
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
|
||||||
|
class BusNetworkDriver(BaseBusDriver):
|
||||||
|
"""network 总线驱动实现(适配)"""
|
||||||
|
def _init_config(self):
|
||||||
|
self.config.setdefault("channel", 0) # CAN通道
|
||||||
|
self.config.setdefault("baudrate", 500000) # 500kbps
|
||||||
|
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
|
||||||
|
logger.info(f"UART驱动配置初始化完成:{self.config}")
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
try:
|
||||||
|
# 实际逻辑:打开CAN通道
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.is_connected = True
|
||||||
|
logger.info("CAN总线连接成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线连接失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.is_connected = False
|
||||||
|
logger.info("CAN总线断开成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线断开失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("CAN驱动未连接,发送失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
|
||||||
|
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
|
||||||
|
logger.info(
|
||||||
|
f"CAN发送数据|帧ID:0x{frame_id:X}|扩展帧:{is_extended}|"
|
||||||
|
f"数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
)
|
||||||
|
# 实际逻辑:调用CAN驱动发送接口
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN数据发送失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("CAN驱动未连接,接收失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 模拟接收
|
||||||
|
time.sleep(0.001)
|
||||||
|
# mock_data = b"\x11\x22\x33\x44"
|
||||||
|
# logger.debug(f"CAN接收数据:{mock_data.hex()}")
|
||||||
|
# return mock_data
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN数据接收失败:{str(e)}")
|
||||||
|
return None
|
||||||
73
midware/drivers/bus_oc.py
Normal file
73
midware/drivers/bus_oc.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
|
||||||
|
oc 驱动示例(bus_oc.py)—— 补充参考
|
||||||
|
"""
|
||||||
|
# midware/drivers/bus_can.py
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
|
||||||
|
class BusOCDriver(BaseBusDriver):
|
||||||
|
"""uart总线驱动实现(适配)"""
|
||||||
|
def _init_config(self):
|
||||||
|
self.config.setdefault("channel", 0) # CAN通道
|
||||||
|
self.config.setdefault("baudrate", 500000) # 500kbps
|
||||||
|
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
|
||||||
|
logger.info(f"UART驱动配置初始化完成:{self.config}")
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
try:
|
||||||
|
# 实际逻辑:打开CAN通道
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.is_connected = True
|
||||||
|
logger.info("CAN总线连接成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线连接失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.is_connected = False
|
||||||
|
logger.info("CAN总线断开成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线断开失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("CAN驱动未连接,发送失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
|
||||||
|
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
|
||||||
|
logger.info(
|
||||||
|
f"CAN发送数据|帧ID:0x{frame_id:X}|扩展帧:{is_extended}|"
|
||||||
|
f"数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
)
|
||||||
|
# 实际逻辑:调用CAN驱动发送接口
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN数据发送失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("CAN驱动未连接,接收失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 模拟接收
|
||||||
|
time.sleep(0.001)
|
||||||
|
# mock_data = b"\x11\x22\x33\x44"
|
||||||
|
# logger.debug(f"CAN接收数据:{mock_data.hex()}")
|
||||||
|
# return mock_data
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN数据接收失败:{str(e)}")
|
||||||
|
return None
|
||||||
131
midware/drivers/bus_uart.py
Normal file
131
midware/drivers/bus_uart.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
"""
|
||||||
|
Tan mingyan
|
||||||
|
2026/3/9
|
||||||
|
|
||||||
|
uart 驱动示例(bus_can.py)—— 补充参考
|
||||||
|
"""
|
||||||
|
# midware/drivers/bus_can.py
|
||||||
|
from midware.config.base_config import get_device_config
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .base_driver import BaseBusDriver
|
||||||
|
from .renode_agent import renode
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
class BusUARTDriver(BaseBusDriver):
|
||||||
|
"""uart总线驱动实现(适配)"""
|
||||||
|
def _init_config(self):
|
||||||
|
self.config.setdefault("channel", 0) # CAN通道
|
||||||
|
self.config.setdefault("baudrate", 500000) # 500kbps
|
||||||
|
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
|
||||||
|
logger.info(f"UART驱动配置初始化完成:{self.config}")
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
try:
|
||||||
|
# 实际逻辑:打开CAN通道
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.is_connected = True
|
||||||
|
logger.info("CAN总线连接成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线连接失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.is_connected = False
|
||||||
|
logger.info("CAN总线断开成功")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"CAN总线断开失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self, data: bytes, **kwargs) -> bool:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("UART驱动未连接,发送失败")
|
||||||
|
return False
|
||||||
|
# 查找当前设备的配置
|
||||||
|
|
||||||
|
# ========== 核心步骤1:从kwargs提取设备名称 ==========
|
||||||
|
|
||||||
|
dev_name = kwargs.get("dev_name")#2026/4/10
|
||||||
|
if not dev_name:
|
||||||
|
logger.error("UART发送失败:kwargs中未传入dev_name(设备名称)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
device_config = get_device_config().get(dev_name)
|
||||||
|
if not device_config:
|
||||||
|
logger.error(f"UART发送失败:设备 {dev_name} 未在DEVICE_CONFIG_DICT中配置")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
#frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
|
||||||
|
#is_extended = kwargs.get("is_extended", False) # 是否扩展帧
|
||||||
|
logger.info(
|
||||||
|
#f"UART发送数据|帧ID:0x{frame_id:X}|扩展帧:{is_extended}|"
|
||||||
|
f"UART发送数据:{data.hex()}|长度:{len(data)}字节"
|
||||||
|
)
|
||||||
|
## 外设名称+函数名+字符串参数 uart24 WriteRXFIFODataString "0xEB9000C571000827C00000BC1003190000106C02AC020101FF5716"'
|
||||||
|
|
||||||
|
sysbus_cmd = f'{dev_name} WriteRXFIFODataString "0x{data.hex()}"'
|
||||||
|
|
||||||
|
#sysbus_cmd = f'can_a GetTxBufferDataString'
|
||||||
|
print(sysbus_cmd)
|
||||||
|
logger.info(f"UART发送指令:{sysbus_cmd}")
|
||||||
|
response = renode.send_sync(sysbus_cmd,timeout_ms=20)#2026/4/10
|
||||||
|
#response = renode.enqueue_cmd(sysbus_cmd)#2026/6/4
|
||||||
|
# sysbus_cmd = f'uart24 GetTXFIFODataString'
|
||||||
|
# print(sysbus_cmd)
|
||||||
|
# response = renode.send_sync(sysbus_cmd)#2026/4/10
|
||||||
|
# 实际逻辑:调用UART驱动发送接口
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UART数据发送失败:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
|
||||||
|
if not self.is_connected:
|
||||||
|
logger.error("UART驱动未连接,接收失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
#遍历所有UART外设,循环发送读取指令:例如 adc1 ReadAllChannelsToString02
|
||||||
|
|
||||||
|
# 模拟接收
|
||||||
|
time.sleep(0.91)
|
||||||
|
# mock_data = b"\x11\x22\x33\x44"
|
||||||
|
# logger.debug(f"CAN接收数据:{mock_data.hex()}")
|
||||||
|
#logger.info(f"CAN接收数据:")
|
||||||
|
# return mock_data
|
||||||
|
'''
|
||||||
|
mock_data = b"\x11\x22\x33\x44"
|
||||||
|
logger.debug(f"AD接收数据:{mock_data.hex()}")
|
||||||
|
|
||||||
|
#2026/4/10
|
||||||
|
dev_name = kwargs.get("dev_name")
|
||||||
|
if not dev_name:
|
||||||
|
logger.error("AD接收失败:kwargs中未传入dev_name(设备名称)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
cfg = DEVICE_CONFIG_DICT[dev_name]
|
||||||
|
base = int(cfg["内存基地址"], 16)
|
||||||
|
offset = int(cfg["内存偏移地址"], 16)
|
||||||
|
addr = base + offset
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# 调用 Renode 读取数据
|
||||||
|
# ======================
|
||||||
|
cmd = f"read 0x{addr:X}"
|
||||||
|
hex_str = renode.send_sync(cmd)
|
||||||
|
#return bytes.fromhex(hex_str.strip())
|
||||||
|
logger.info(f"AD接收数据:{mock_data.hex()}")
|
||||||
|
#return mock_data
|
||||||
|
'''
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UART数据接收失败:{str(e)}")
|
||||||
|
return None
|
||||||
528
midware/drivers/renode_agent.py
Normal file
528
midware/drivers/renode_agent.py
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
"""
|
||||||
|
2026/4/10
|
||||||
|
TMY
|
||||||
|
在connect_renode_19.py的基础上,添加单例模式,并添加总线类型映射
|
||||||
|
"""
|
||||||
|
# midware/drivers/renode_agent.py
|
||||||
|
import threading
|
||||||
|
import binascii
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import re
|
||||||
|
import statistics
|
||||||
|
from typing import Optional, Callable, List, Dict
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# 这里放你原来 connect_renode_19.py 的全部代码
|
||||||
|
# 只加 2 个东西:单例 + 总线类型映射
|
||||||
|
# ======================
|
||||||
|
# from . import DRIVER_MAPPING
|
||||||
|
# from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
BUS_TYPE_MAP = {
|
||||||
|
0x11: "UART",
|
||||||
|
0x22: "CAN",
|
||||||
|
0x33: "1553B",
|
||||||
|
0x44: "NET",
|
||||||
|
0x55: "AD",
|
||||||
|
0x66: "OC",
|
||||||
|
}
|
||||||
|
# 合法的总线类型第一个字节(字符串形式,方便判断)
|
||||||
|
LEGAL_BUS_PREFIX = {"11", "22", "33", "44", "55", "66"}
|
||||||
|
|
||||||
|
class RenodeAgent:
|
||||||
|
_instance = None
|
||||||
|
_lock = threading.Lock()
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
renode_path="renode",
|
||||||
|
script_path=None,
|
||||||
|
max_queue=50, # 指令队列最大长度(限流)
|
||||||
|
reconnect_retries=5, # 自动重连次数
|
||||||
|
socket_timeout=3):
|
||||||
|
if hasattr(self, "inited"):
|
||||||
|
return
|
||||||
|
self.inited = True
|
||||||
|
|
||||||
|
# ========= 你的原有代码 =========
|
||||||
|
# self.process = ...
|
||||||
|
# self.async_queue = ...
|
||||||
|
# self.start_renode()
|
||||||
|
# self.start_async_listener()
|
||||||
|
# 基础配置
|
||||||
|
self.renode_path = renode_path
|
||||||
|
self.script_path = script_path
|
||||||
|
self.process: Optional[subprocess.Popen] = None
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
# 输出与异步
|
||||||
|
self.output_buffer: List[str] = []
|
||||||
|
self.async_running = False
|
||||||
|
self.async_callback: Optional[Callable[[str], None]] = None
|
||||||
|
|
||||||
|
# 限流队列
|
||||||
|
self.max_queue = max_queue
|
||||||
|
self.cmd_queue: List[str] = []
|
||||||
|
self.busy = False
|
||||||
|
|
||||||
|
# 自动重连
|
||||||
|
self.reconnect_retries = reconnect_retries
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
|
# 时延统计
|
||||||
|
self.latency_stats: Dict[str, List[float]] = {
|
||||||
|
"send": [], "process": [], "total": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# ====================== 超强过滤规则 ======================
|
||||||
|
self.filter_re = re.compile(
|
||||||
|
r"\(.*?\)" # 过滤 (machine) (TestPlatform) 等
|
||||||
|
r"|\d{2}:\d{2}:\d{2}\.\d+" # 过滤时间 20:15:26.1234
|
||||||
|
r"|renode>|\$|->" # 过滤提示符
|
||||||
|
r"|\x1b\[[0-9;]*m" # 过滤颜色码
|
||||||
|
r"|^\s*$" # 过滤空行
|
||||||
|
)
|
||||||
|
|
||||||
|
self.last_send_t =0.0
|
||||||
|
self.send_min_gap = 0.01 #控制下发间隔
|
||||||
|
|
||||||
|
'''
|
||||||
|
# 你原来的函数,完全不变
|
||||||
|
def send_sync(self, cmd: str, timeout=1.0):
|
||||||
|
# 你的原有逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def enqueue_cmd(self, cmd: str):
|
||||||
|
# 你的原有逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_async_listener(self, callback):
|
||||||
|
# 你的原有异步监听
|
||||||
|
pass
|
||||||
|
'''
|
||||||
|
def start(self) -> bool:
|
||||||
|
try:
|
||||||
|
args = [
|
||||||
|
self.renode_path,
|
||||||
|
"--console",
|
||||||
|
#"--disable-ansi-colors",
|
||||||
|
"-e", "set echo off; set line-editing off; set raw on"
|
||||||
|
]
|
||||||
|
if self.script_path:
|
||||||
|
args.extend(["-e", f"include @{self.script_path}"])
|
||||||
|
#args.extend(["-e", f"{self.script_path}"])
|
||||||
|
|
||||||
|
self.process = subprocess.Popen(
|
||||||
|
args,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
bufsize=0,
|
||||||
|
universal_newlines=True,
|
||||||
|
creationflags=subprocess.CREATE_NEW_CONSOLE #LWB
|
||||||
|
)
|
||||||
|
|
||||||
|
self.async_running = True
|
||||||
|
self.connected = True
|
||||||
|
threading.Thread(target=self._read_thread, daemon=True).start()
|
||||||
|
threading.Thread(target=self._queue_worker, daemon=True).start()
|
||||||
|
time.sleep(1)
|
||||||
|
self.clear_buffers()
|
||||||
|
print("✅ 仿真平台 启动成功")
|
||||||
|
|
||||||
|
sysbus_cmd = "logLevel 3" #3级-只显示error
|
||||||
|
response = renode.send_sync(sysbus_cmd)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 启动失败: {e}")
|
||||||
|
self.connected = False
|
||||||
|
return self._try_reconnect()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 后台读取线程(永远实时读取,永不堆积)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def _read_thread(self):
|
||||||
|
while self.async_running and self.process.poll() is None:
|
||||||
|
try:
|
||||||
|
line = self.process.stdout.readline()
|
||||||
|
if not line:
|
||||||
|
time.sleep(0.001)
|
||||||
|
continue
|
||||||
|
|
||||||
|
cleaned = self.filter_line(line)
|
||||||
|
if not cleaned:
|
||||||
|
continue
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
self.output_buffer.append(cleaned)
|
||||||
|
|
||||||
|
if self.async_callback:
|
||||||
|
self.async_callback(cleaned)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.connected = False
|
||||||
|
print("⚠️ Renode 已断开")
|
||||||
|
self._try_reconnect()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 过滤垃圾输出
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def filter_line(self, line: str) -> Optional[str]:
|
||||||
|
res = self.filter_re.sub("", line).strip()
|
||||||
|
return res if res else None
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 同步发送(带时延测量 + 限流),
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def send_sync(
|
||||||
|
self,
|
||||||
|
cmd: str,
|
||||||
|
timeout_ms: int = 20
|
||||||
|
) -> tuple[Optional[str], float, float, float]:
|
||||||
|
if not self.connected or not self.process:
|
||||||
|
return None, 0, 0, 0
|
||||||
|
|
||||||
|
# 限流:队列满则等待
|
||||||
|
while len(self.cmd_queue) >= self.max_queue:
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
# self.clear_buffers() #同步发送不再清空全局输出缓冲区,缓冲区只有一部读取线程消费 2026/6/3
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
|
||||||
|
# 发送
|
||||||
|
try:
|
||||||
|
self.process.stdin.write(f"{cmd}\n")
|
||||||
|
self.process.stdin.flush()
|
||||||
|
except:
|
||||||
|
return None, 0, 0, 0
|
||||||
|
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
|
||||||
|
# 等待结果
|
||||||
|
timeout = time.time() + timeout_ms / 1000
|
||||||
|
while time.time() < timeout:
|
||||||
|
with self.lock:
|
||||||
|
if self.output_buffer:
|
||||||
|
res = "\n".join(self.output_buffer)
|
||||||
|
self.output_buffer.clear()
|
||||||
|
t2 = time.perf_counter()
|
||||||
|
|
||||||
|
send_lat = (t1 - t0) * 1000
|
||||||
|
proc_lat = (t2 - t1) * 1000
|
||||||
|
total_lat = (t2 - t0) * 1000
|
||||||
|
|
||||||
|
self.latency_stats["send"].append(send_lat)
|
||||||
|
self.latency_stats["process"].append(proc_lat)
|
||||||
|
self.latency_stats["total"].append(total_lat)
|
||||||
|
|
||||||
|
return res, send_lat, proc_lat, total_lat
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
return None, 0, 0, 0
|
||||||
|
|
||||||
|
'''
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 同步发送(有时延测量 + 限流,不等待回复),
|
||||||
|
# 2026/6/4
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def send_sync(
|
||||||
|
self,
|
||||||
|
cmd: str,
|
||||||
|
timeout_ms: int = 20
|
||||||
|
) -> tuple[Optional[str], float, float, float]:
|
||||||
|
if not self.connected or not self.process:
|
||||||
|
return None,0,0,0
|
||||||
|
#队列限流等待
|
||||||
|
while len(self.cmd_queue) >=self.max_queue:
|
||||||
|
time.sleep(0.001)
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.process.stdin.write(f"{cmd}\n")
|
||||||
|
self.process.stdin.flush()
|
||||||
|
|
||||||
|
except:
|
||||||
|
return None,0,0,0
|
||||||
|
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
|
||||||
|
#【关键改动:不在阻塞轮询收应答,直接退出;所有应答给dispacher消费】
|
||||||
|
send_lat = (t1 - t0)*1000
|
||||||
|
|
||||||
|
return None,send_lat, 0 , send_lat
|
||||||
|
'''
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 异步监听
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def start_async_listener(self, callback: Callable[[str], None]):
|
||||||
|
self.async_callback = callback
|
||||||
|
|
||||||
|
def clear_buffers(self):
|
||||||
|
with self.lock:
|
||||||
|
self.output_buffer.clear()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 指令队列限流(后台异步发送)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def enqueue_cmd(self, cmd: str):
|
||||||
|
if len(self.cmd_queue) < self.max_queue:
|
||||||
|
self.cmd_queue.append(cmd)
|
||||||
|
'''
|
||||||
|
def _queue_worker(self): #老版本,停用 2026、6、4
|
||||||
|
while self.async_running:
|
||||||
|
if not self.connected or self.busy or not self.cmd_queue:
|
||||||
|
time.sleep(0.001)
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.busy = True
|
||||||
|
cmd = self.cmd_queue.pop(0)
|
||||||
|
self.send_sync(cmd, timeout_ms=1) #150->1
|
||||||
|
self.busy = False
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
def _queue_worker(self): #新版本,2026、6、4
|
||||||
|
while self.async_running:
|
||||||
|
if not self.connected or not self.cmd_queue:
|
||||||
|
time.sleep(0.001)
|
||||||
|
continue
|
||||||
|
|
||||||
|
cmd = self.cmd_queue.pop(0)
|
||||||
|
self.send_sync(cmd) #150->1
|
||||||
|
logger.info(f" _queue_worker向管道发送数据:{cmd}")
|
||||||
|
'''
|
||||||
|
def _queue_worker(self): #新版本,2026、6、4
|
||||||
|
while self.async_running:
|
||||||
|
now = time.time()
|
||||||
|
#时间没有到,让出CPU
|
||||||
|
if now - self.last_send_t < self.send_min_gap:
|
||||||
|
time.sleep(0.0005)
|
||||||
|
continue
|
||||||
|
if not self.connected or not self.cmd_queue:
|
||||||
|
time.sleep(0.001)
|
||||||
|
continue
|
||||||
|
|
||||||
|
cmd = self.cmd_queue.pop(0)
|
||||||
|
self.send_sync(cmd) #150->1
|
||||||
|
logger.info(f" _queue_worker向管道发送数据:{cmd}")
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 自动重连
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def _try_reconnect(self) -> bool:
|
||||||
|
print(f"🔌 尝试自动重连 ({self.reconnect_retries} 次)")
|
||||||
|
for i in range(self.reconnect_retries):
|
||||||
|
self.stop()
|
||||||
|
time.sleep(1)
|
||||||
|
if self.start():
|
||||||
|
print(f"✅ 第 {i+1} 次重连成功")
|
||||||
|
return True
|
||||||
|
time.sleep(1)
|
||||||
|
print("❌ 重连全部失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 时延统计
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def get_latency_report(self) -> Dict[str, float]:
|
||||||
|
def stats(arr):
|
||||||
|
if not arr: return 0,0,0
|
||||||
|
return round(statistics.mean(arr),2), round(max(arr),2), round(min(arr),2)
|
||||||
|
return {
|
||||||
|
"send_avg,max,min": stats(self.latency_stats["send"]),
|
||||||
|
"process_avg,max,min": stats(self.latency_stats["process"]),
|
||||||
|
"total_avg,max,min": stats(self.latency_stats["total"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
def clear_latency(self):
|
||||||
|
for k in self.latency_stats:
|
||||||
|
self.latency_stats[k].clear()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 停止
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
def stop(self):
|
||||||
|
self.async_running = False
|
||||||
|
self.connected = False
|
||||||
|
try:
|
||||||
|
if self.process:
|
||||||
|
self.process.stdin.write("quit\n")
|
||||||
|
self.process.stdin.flush()
|
||||||
|
time.sleep(0.2)
|
||||||
|
self.process.terminate()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.process = None
|
||||||
|
print("🔌 Renode 已停止")
|
||||||
|
# midware/drivers/renode_agent.py
|
||||||
|
|
||||||
|
|
||||||
|
# 第三步:异步数据分发(完全不影响你现有架构)
|
||||||
|
#在 renode_agent.py 里加全局分发回调,直接把数据投递到你现有的驱动 recv
|
||||||
|
# def renode_async_data_dispatcher(line: str):
|
||||||
|
# try:
|
||||||
|
# # 1. 转成字节
|
||||||
|
|
||||||
|
# byte_data = binascii.unhexlify(line.strip())
|
||||||
|
# print(byte_data)
|
||||||
|
|
||||||
|
# # 2. 解析头部 3 字节
|
||||||
|
# if len(byte_data) < 3:
|
||||||
|
# return
|
||||||
|
# bus_type_id = byte_data[0]
|
||||||
|
# machine_type = byte_data[1]
|
||||||
|
# machine_id = byte_data[2]
|
||||||
|
# payload = byte_data[3:]
|
||||||
|
|
||||||
|
# # 3. 映射协议类型
|
||||||
|
# protocol = BUS_TYPE_MAP.get(bus_type_id)
|
||||||
|
# if not protocol:
|
||||||
|
# return
|
||||||
|
|
||||||
|
# # 4. 找到对应设备(根据单机编号)
|
||||||
|
# dev_name = None
|
||||||
|
# for name, cfg in DEVICE_CONFIG_DICT.items():
|
||||||
|
# if cfg.get("协议类型") == protocol and cfg.get("单机编号") == machine_id:
|
||||||
|
# dev_name = name
|
||||||
|
# break
|
||||||
|
# if not dev_name:
|
||||||
|
# return
|
||||||
|
|
||||||
|
# # 5. 把数据 **直接放入你现有的总线接收队列**
|
||||||
|
# from midware.core.bus_udp_decoupler import bus_recv_queues
|
||||||
|
# q = bus_recv_queues[protocol]
|
||||||
|
# q.put({
|
||||||
|
# "dev_name": dev_name,
|
||||||
|
# "raw_data": payload,
|
||||||
|
# "protocol": protocol,
|
||||||
|
# })
|
||||||
|
|
||||||
|
# except Exception:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
def renode_async_data_dispatcher(line: str):
|
||||||
|
try:
|
||||||
|
# -----------------------
|
||||||
|
# 🔥 延迟导入(放在函数内部,不会循环引用!)
|
||||||
|
# -----------------------
|
||||||
|
from midware.core.bus_udp_decoupler import bus_recv_queues
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
# print(f"异步监听到的数据{line}")
|
||||||
|
logger.info(
|
||||||
|
f"异步监听到的数据{line}"
|
||||||
|
)
|
||||||
|
# -----------------------
|
||||||
|
# 0.1. 基础过滤
|
||||||
|
# -----------------------
|
||||||
|
line = line.strip()
|
||||||
|
if len(line) < 6: # 至少3字节 = 6个十六进制字符
|
||||||
|
return
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# 0.2. 【关键】判断前2个字符是否是合法总线类型
|
||||||
|
# -----------------------
|
||||||
|
first_byte_str = line[:2]
|
||||||
|
if first_byte_str not in LEGAL_BUS_PREFIX:
|
||||||
|
# 不是我们要的总线数据 → 直接丢弃
|
||||||
|
return
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# 0.3. 判断整个字符串是否是合法十六进制(安全转换)
|
||||||
|
# -----------------------
|
||||||
|
def is_hex(s):
|
||||||
|
try:
|
||||||
|
int(s, 16)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not is_hex(line):
|
||||||
|
return
|
||||||
|
|
||||||
|
# 1. 把 Renode 返回的字符串转成字节,首先判断首字符是否为
|
||||||
|
byte_data = bytes.fromhex(line.strip())
|
||||||
|
|
||||||
|
if len(byte_data) < 3:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. 解析 3 字节头部
|
||||||
|
bus_type_id = byte_data[0]
|
||||||
|
machine_type = byte_data[1]
|
||||||
|
machine_id = byte_data[2]
|
||||||
|
payload = byte_data[3:]
|
||||||
|
|
||||||
|
# 3. 映射协议名
|
||||||
|
protocol = BUS_TYPE_MAP.get(bus_type_id)
|
||||||
|
if not protocol:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 4. 根据 协议类型 + 单机编号 → 找到设备名
|
||||||
|
dev_name = None
|
||||||
|
for name, cfg in DEVICE_CONFIG_DICT.items():
|
||||||
|
if (cfg.get("protocol") == protocol and
|
||||||
|
cfg.get("number") == machine_id):
|
||||||
|
dev_name = name
|
||||||
|
break
|
||||||
|
|
||||||
|
if not dev_name:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 5. 放入你现有的接收队列(完全兼容你原来架构)
|
||||||
|
q = bus_recv_queues[protocol]
|
||||||
|
try:
|
||||||
|
q.put_nowait({
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": payload,
|
||||||
|
"protocol": protocol,
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
# 在 renode_agent.py 末尾加这个函数
|
||||||
|
def start_renode_uart_redirect():
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
renode = RenodeAgent() # 单例
|
||||||
|
|
||||||
|
for dev_name, cfg in DEVICE_CONFIG_DICT.items():
|
||||||
|
tcp_local = cfg.get("tcp_local_port", 1)
|
||||||
|
tcp_bus = cfg.get("tcp_bus_port", 1)
|
||||||
|
|
||||||
|
if tcp_local != 1 and tcp_bus != 1:
|
||||||
|
term_name = f"term_{dev_name}"
|
||||||
|
cmd1 = f'emulation CreateServerSocketTerminal {tcp_bus} "{term_name}"'#临时注释2026/6/2
|
||||||
|
cmd2 = f'connector Connect sysbus.{dev_name} {term_name}'
|
||||||
|
|
||||||
|
print(f"[Renode] 配置UART重定向: {cmd1}")
|
||||||
|
renode.send_sync(cmd1, timeout_ms=2)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
print(f"[Renode] 连接外设: {cmd2}")
|
||||||
|
renode.send_sync(cmd2, timeout_ms=2)
|
||||||
|
time.sleep(0.2)
|
||||||
|
print("[Renode] UART重定向配置完成!")
|
||||||
|
|
||||||
|
# 全局单例,全局唯一
|
||||||
|
renode = RenodeAgent(
|
||||||
|
renode_path = r"E:\simulation\1_simulation\卫星仿真平台_后端.exe", #r"E:\codes\1_simulation\卫星仿真平台_后端.exe", #r"C:\Users\PingCe\Desktop\1_simulation\卫星仿真平台_后端.exe", # Windows可写绝对路径如 C:/Renode/renode.exe
|
||||||
|
script_path = r'E:\simulation\1_simulation\generated.resc',#r'E:\codes\1_simulation\generated_771_ADC1.resc', #r'include @C:\Users\PingCe\Desktop\1_simulation\generated_771_ADC1.resc',
|
||||||
|
max_queue=25, # 最多同时排队5条指令,防止压爆
|
||||||
|
reconnect_retries=5
|
||||||
|
)
|
||||||
226
midware/drivers/tcp_uart_agent.py
Normal file
226
midware/drivers/tcp_uart_agent.py
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
from queue import Full
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from typing import Dict, Optional
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
import telnetlib #2026/6/2
|
||||||
|
|
||||||
|
# 全局:TCP客户端管理(每个UART一个实例)
|
||||||
|
tcp_clients: Dict[str, "TCPUARTClient"] = {}
|
||||||
|
# HEAD = b"\xeb\x90" #地测帧头
|
||||||
|
# TAIL = b"\x57\x16" #地测帧尾
|
||||||
|
# MIN_FRAME = 23 ##地测长度最小值
|
||||||
|
# MAX_FRAME = 1000 ##地测帧长度最大值
|
||||||
|
|
||||||
|
class TCPUARTClient:
|
||||||
|
def __init__(self, dev_name: str, local_port: int, remote_port: int, remote_ip="127.0.0.1"):
|
||||||
|
self.dev_name = dev_name
|
||||||
|
self.local_port = local_port
|
||||||
|
self.remote_port = remote_port
|
||||||
|
self.remote_ip = remote_ip
|
||||||
|
self.socket = None
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
|
||||||
|
self.HEAD = b"\xeb\x90" #地测帧头
|
||||||
|
self.TAIL = b"\x57\x16" #地测帧尾
|
||||||
|
self.MIN_LEN = 23 ##地测长度最小值
|
||||||
|
self.MAX_LEN = 1000 ##地测帧长度最大值
|
||||||
|
self.tcp_buf = b''
|
||||||
|
|
||||||
|
#self.tn = None
|
||||||
|
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
try:
|
||||||
|
|
||||||
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
# 关键点:禁止Nagle算法,模仿网络调试助手,强制使用纯raw socket连接,但是导致TCP收不到数据 2026/6/2
|
||||||
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #停用
|
||||||
|
#self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) #telnet
|
||||||
|
self.socket.bind(("127.0.0.1", self.local_port))
|
||||||
|
self.socket.connect((self.remote_ip, self.remote_port))
|
||||||
|
self.socket.setblocking(False)
|
||||||
|
|
||||||
|
self.running = True
|
||||||
|
print(f"[TCP-UART] {self.dev_name} 连接成功: local={self.local_port} remote={self.remote_port}")
|
||||||
|
|
||||||
|
#telnet
|
||||||
|
#self.tn = telnetlib.Telnet("127.0.0.1",{self.remote_port},timeout=5)
|
||||||
|
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[TCP-UART] {self.dev_name} 连接失败: {e}")
|
||||||
|
return False
|
||||||
|
'''
|
||||||
|
# 去除转义FF的函数 2026/6/2,可能有安全问题
|
||||||
|
def unescape_ff(raw:bytes)->bytes:
|
||||||
|
buf = bytearray()
|
||||||
|
i = 0
|
||||||
|
n = len(raw)
|
||||||
|
while i<n:
|
||||||
|
buf.append(raw[i])
|
||||||
|
|
||||||
|
#当前字节FF,下一个字节FF:跳过下一个字节,只保留一个
|
||||||
|
if raw[i] == 0xFF and i+1<n and raw[i+1]==0xFF:
|
||||||
|
i +=1
|
||||||
|
i +=1
|
||||||
|
return bytes(buf)
|
||||||
|
'''
|
||||||
|
# 去除转义FF的函数 2026/6/2
|
||||||
|
def unescape_ff(self, raw:bytes)->bytes:
|
||||||
|
out = bytearray()
|
||||||
|
idx=0
|
||||||
|
length = len(raw)
|
||||||
|
while idx<length:
|
||||||
|
cur = raw[idx]
|
||||||
|
out.append(cur)
|
||||||
|
if cur == 0xff and (idx + 1 < length) and raw[idx+1]==0xff:
|
||||||
|
idx+=1
|
||||||
|
idx+=1
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
def receive_loop(self):
|
||||||
|
from midware.core.bus_udp_decoupler import bus_recv_queues
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
|
||||||
|
#data = self.tn.read_some()
|
||||||
|
|
||||||
|
data = self.socket.recv(8192)#4096修改为16384 2026/5/12 tmy 65536->8192 2026/5/15
|
||||||
|
if not data:
|
||||||
|
time.sleep(0.001)
|
||||||
|
continue
|
||||||
|
self.tcp_buf += data #新数据追加到缓存尾部2026/6/2
|
||||||
|
|
||||||
|
dev_name = None
|
||||||
|
protocol = None
|
||||||
|
for name, cfg in DEVICE_CONFIG_DICT.items():
|
||||||
|
if cfg.get("tcp_bus_port") == self.remote_port:
|
||||||
|
dev_name = name
|
||||||
|
protocol = cfg.get("protocol")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not dev_name or not protocol:
|
||||||
|
continue
|
||||||
|
|
||||||
|
#循环拆包,大多数情况是只跑0~1次循环 2026/6/2
|
||||||
|
while True:
|
||||||
|
if len(self.tcp_buf)>self.MAX_LEN * 2:
|
||||||
|
self.tcp_buf = self.tcp_buf[-1000:]
|
||||||
|
h_pos = self.tcp_buf.find(self.HEAD)
|
||||||
|
if h_pos ==-1:
|
||||||
|
break
|
||||||
|
#截断帧头前面垃圾
|
||||||
|
if h_pos>0:
|
||||||
|
self.tcp_buf = self.tcp_buf[h_pos:]
|
||||||
|
#缓存不足最小帧,直接退出
|
||||||
|
if(len(self.tcp_buf)<self.MIN_LEN):
|
||||||
|
break
|
||||||
|
#从帧头向后查找结束符号
|
||||||
|
t_pos = self.tcp_buf.find(self.TAIL,2)
|
||||||
|
if t_pos == -1:
|
||||||
|
break
|
||||||
|
frame = self.tcp_buf[:t_pos+2]
|
||||||
|
#logger.info(f'去除转义FF帧前的有问题帧内容:{frame.hex()}')
|
||||||
|
|
||||||
|
if(self.MIN_LEN<= len(frame) <= self.MAX_LEN):
|
||||||
|
#去除转义FF
|
||||||
|
#logger.info(f'去除转义FF帧内容:')
|
||||||
|
real_data = self.unescape_ff(frame)
|
||||||
|
|
||||||
|
#logger.info(f'去除转义FF帧内容:{real_data.hex()}')
|
||||||
|
#入队
|
||||||
|
try:
|
||||||
|
q = bus_recv_queues.get(protocol)
|
||||||
|
if q:
|
||||||
|
try:
|
||||||
|
q.put_nowait({
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": real_data,
|
||||||
|
"protocol": protocol,
|
||||||
|
})
|
||||||
|
logger.debug(f'接受TCP重定向数据,存入bus_recv_queues,内容:{real_data.hex()}')
|
||||||
|
except Full as e:
|
||||||
|
print("队列已满",e)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("其他异常",repr(e))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#切掉已经解析的帧
|
||||||
|
self.tcp_buf = self.tcp_buf[(t_pos+2) :]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
# 匹配设备名
|
||||||
|
dev_name = None
|
||||||
|
protocol = None
|
||||||
|
for name, cfg in DEVICE_CONFIG_DICT.items():
|
||||||
|
if cfg.get("tcp_bus_port") == self.remote_port:
|
||||||
|
dev_name = name
|
||||||
|
protocol = cfg.get("protocol")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not dev_name or not protocol:
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f'接受TCP重定向数据,存入bus_recv_queues,内容:{data.hex()}')
|
||||||
|
|
||||||
|
|
||||||
|
# 推入现有接收队列(完全兼容你原有架构)
|
||||||
|
q = bus_recv_queues.get(protocol)
|
||||||
|
if q:
|
||||||
|
try:
|
||||||
|
q.put_nowait({
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"raw_data": data,
|
||||||
|
"protocol": protocol,
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
'''
|
||||||
|
except BlockingIOError:
|
||||||
|
time.sleep(0.001)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[TCP-UART] {self.dev_name} 接收异常: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
if self.connect():
|
||||||
|
self.thread = threading.Thread(target=self.receive_loop, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
tcp_clients[self.dev_name] = self
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
if self.socket:
|
||||||
|
self.socket.close()
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# 批量启动所有UART TCP客户端
|
||||||
|
# ======================
|
||||||
|
def start_all_tcp_uart_clients():
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
|
||||||
|
for dev_name, cfg in DEVICE_CONFIG_DICT.items():
|
||||||
|
tcp_local = cfg.get("tcp_local_port", 1)
|
||||||
|
tcp_bus = cfg.get("tcp_bus_port", 1)
|
||||||
|
|
||||||
|
# 只启动端口 !=1 的UART设备
|
||||||
|
if tcp_local != 1 and tcp_bus != 1:
|
||||||
|
client = TCPUARTClient(
|
||||||
|
dev_name=dev_name,
|
||||||
|
local_port=tcp_local,
|
||||||
|
remote_port=tcp_bus
|
||||||
|
)
|
||||||
|
client.start()
|
||||||
|
time.sleep(0.1)
|
||||||
0
midware/network/__init__.py
Normal file
0
midware/network/__init__.py
Normal file
BIN
midware/network/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/network/__pycache__/protocol_dispatcher.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/protocol_dispatcher.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/network/__pycache__/udp_handler.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/udp_handler.cpython-311.pyc
Normal file
Binary file not shown.
191
midware/network/protocol_dispatcher.py
Normal file
191
midware/network/protocol_dispatcher.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# # -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Author; Tan Mingyan
|
||||||
|
@Time : 2026/2/27
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
UDP数据协议分发处理器
|
||||||
|
功能:解析udp_server.recv_multi_udp_hex()返回的数据,按protocol字段分发到对应处理函数
|
||||||
|
支持协议:UART、SYNC、CAN、1553B、AD、OC、网络
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
# 设置标准输出和标准错误的编码为UTF-8
|
||||||
|
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
# sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 获取当前文件的目录
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# 获取上级目录(项目根目录)
|
||||||
|
parent_dir = os.path.dirname(current_dir)
|
||||||
|
# 将项目根目录添加到系统路径
|
||||||
|
sys.path.append(parent_dir)
|
||||||
|
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||||
|
|
||||||
|
import midware.config.base_config as base_config
|
||||||
|
#from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
|
||||||
|
|
||||||
|
# ==================== 协议类型常量(统一管理,避免硬编码)====================
|
||||||
|
# 与你要求的协议类型严格对应
|
||||||
|
PROTOCOL_UART = "UART"
|
||||||
|
PROTOCOL_SYNC = "SYNC"
|
||||||
|
PROTOCOL_CAN = "CAN"
|
||||||
|
PROTOCOL_1553B = "1553B"
|
||||||
|
PROTOCOL_AD = "AD"
|
||||||
|
PROTOCOL_OC = "OC"
|
||||||
|
PROTOCOL_NETWORK = "网络"
|
||||||
|
|
||||||
|
# 支持的协议列表(用于合法性校验)
|
||||||
|
SUPPORTED_PROTOCOLS = [
|
||||||
|
PROTOCOL_UART,
|
||||||
|
PROTOCOL_SYNC,
|
||||||
|
PROTOCOL_CAN,
|
||||||
|
PROTOCOL_1553B,
|
||||||
|
PROTOCOL_AD,
|
||||||
|
PROTOCOL_OC,
|
||||||
|
PROTOCOL_NETWORK
|
||||||
|
]
|
||||||
|
|
||||||
|
# ==================== 各协议处理函数(预留接口,按需填充逻辑)====================
|
||||||
|
def handle_uart(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理UART协议数据"""
|
||||||
|
logger.info(f"[UART处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
|
||||||
|
# TODO: 补充UART数据解析、转发、故障注入等业务逻辑
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_sync(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理SYNC(同步)协议数据"""
|
||||||
|
logger.info(f"[SYNC处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
|
||||||
|
# TODO: 补充SYNC同步数据解析、时序控制等业务逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_can(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理CAN协议数据"""
|
||||||
|
logger.info(f"[CAN处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
|
||||||
|
# TODO: 补充CAN帧解析、总线转发等业务逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_1553b(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理1553B协议数据"""
|
||||||
|
logger.info(f"[1553B处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
|
||||||
|
# TODO: 补充1553B总线数据解析、RT/BC交互等业务逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_ad(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理AD(模拟量输入)协议数据"""
|
||||||
|
logger.info(f"[AD处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
|
||||||
|
# TODO: 补充AD采样值解析、校准、越限告警等业务逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_oc(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理OC(数字量输出)协议数据"""
|
||||||
|
logger.info(f"[OC处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
|
||||||
|
# TODO: 补充OC通道控制、状态反馈、故障模拟等业务逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_network(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理网络协议数据"""
|
||||||
|
logger.info(f"[网络处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
|
||||||
|
# TODO: 补充网络包转发、IP/TCP解析、网络故障注入等业务逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_unknown_protocol(data: Dict[str, Any]) -> None:
|
||||||
|
"""处理未知协议数据(容错兜底)"""
|
||||||
|
unknown_protocol = data.get('protocol', '未知')
|
||||||
|
logger.warning(
|
||||||
|
f"[未知协议] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | "
|
||||||
|
f"协议类型:{unknown_protocol} | 支持的协议列表:{SUPPORTED_PROTOCOLS}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ==================== 核心分发函数(对外暴露的唯一入口)====================
|
||||||
|
def dispatch_udp_data(recv_data: List[Dict[str, Any]]) -> None:
|
||||||
|
"""
|
||||||
|
核心分发函数:遍历recv_data,按protocol分发到对应处理函数
|
||||||
|
:param recv_data: udp_server.recv_multi_udp_hex()返回的原始数据列表,每条数据格式:
|
||||||
|
{"port": 端口号, "dev_name": 外设名, "protocol": 协议类型, "raw_data": 原始二进制数据}
|
||||||
|
"""
|
||||||
|
# 第一步:校验输入数据类型
|
||||||
|
if not isinstance(recv_data, list):
|
||||||
|
logger.error("分发失败:输入的recv_data不是列表类型")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 第二步:空数据直接返回(避免无意义日志)
|
||||||
|
if not recv_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 第三步:构建协议-处理函数映射表(核心:新增/修改协议只需改此表)
|
||||||
|
protocol_handler_map = {
|
||||||
|
PROTOCOL_UART: handle_uart,
|
||||||
|
PROTOCOL_SYNC: handle_sync,
|
||||||
|
PROTOCOL_CAN: handle_can,
|
||||||
|
PROTOCOL_1553B: handle_1553b,
|
||||||
|
PROTOCOL_AD: handle_ad,
|
||||||
|
PROTOCOL_OC: handle_oc,
|
||||||
|
PROTOCOL_NETWORK: handle_network
|
||||||
|
}
|
||||||
|
|
||||||
|
# 第四步:遍历每条数据,按协议分发处理
|
||||||
|
for single_data in recv_data:
|
||||||
|
try:
|
||||||
|
# 容错:确保protocol字段存在且为字符串
|
||||||
|
protocol = single_data.get("protocol", "").strip().upper()
|
||||||
|
port = single_data["port"]
|
||||||
|
dev_name = single_data["dev_name"]
|
||||||
|
# protocol = data["protocol"]
|
||||||
|
raw_data = single_data["raw_data"]
|
||||||
|
|
||||||
|
#根据dev_name,获取内存基地址,偏移地址,发送缓存区大小,接收缓存区大小
|
||||||
|
#dev_info = DEVICE_CONFIG_DICT.get(dev_name, {})
|
||||||
|
if dev_name not in base_config.DEVICE_CONFIG_DICT:
|
||||||
|
logger.error(f"UART外设不存在:{dev_name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取外设基地址、偏移地址
|
||||||
|
base_addr = base_config.DEVICE_CONFIG_DICT[device_name]["base_addr"]
|
||||||
|
offset_addr = base_config.DEVICE_CONFIG_DICT[device_name]["offset_addr"]
|
||||||
|
print(f"sysbus WriteDoubleWord {base_addr + offset_addr} {raw_data}")
|
||||||
|
|
||||||
|
# 兼容协议名大小写(如"can"→"CAN"、"sync"→"SYNC")
|
||||||
|
if protocol in protocol_handler_map:
|
||||||
|
# 调用对应处理函数
|
||||||
|
protocol_handler_map[protocol](single_data)
|
||||||
|
else:
|
||||||
|
# 未知协议调用兜底函数
|
||||||
|
handle_unknown_protocol(single_data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 单条数据处理异常不影响整体,记录错误日志
|
||||||
|
logger.error(
|
||||||
|
f"[数据处理异常] 外设:{single_data.get('dev_name', '未知')} | 错误信息:{str(e)}",
|
||||||
|
exc_info=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# ==================== 测试用例(验证分发逻辑是否正常)====================
|
||||||
|
def test_dispatcher():
|
||||||
|
"""模拟udp_server.recv_multi_udp_hex()返回的数据,测试分发逻辑"""
|
||||||
|
# 模拟接收数据
|
||||||
|
mock_recv_data = [
|
||||||
|
{"port": 8880, "dev_name": "Uart0", "protocol": "uart", "raw_data": b"UART_TEST_DATA"},
|
||||||
|
{"port": 8881, "dev_name": "Sync0", "protocol": "SYNC", "raw_data": b"SYNC_TEST_DATA"},
|
||||||
|
{"port": 8882, "dev_name": "Can1", "protocol": "Can", "raw_data": b"CAN_TEST_DATA"},
|
||||||
|
{"port": 8883, "dev_name": "1553B0", "protocol": "1553B", "raw_data": b"1553B_TEST_DATA"},
|
||||||
|
{"port": 8884, "dev_name": "AD0", "protocol": "ad", "raw_data": b"AD_TEST_DATA"},
|
||||||
|
{"port": 8885, "dev_name": "OC0", "protocol": "OC", "raw_data": b"OC_TEST_DATA"},
|
||||||
|
{"port": 8886, "dev_name": "Net0", "protocol": "网络", "raw_data": b"NETWORK_TEST_DATA"},
|
||||||
|
{"port": 8887, "dev_name": "Unknown0", "protocol": "SPI", "raw_data": b"UNKNOWN_TEST_DATA"},
|
||||||
|
]
|
||||||
|
# 执行分发
|
||||||
|
logger.info("开始测试协议分发逻辑...")
|
||||||
|
dispatch_udp_data(mock_recv_data)
|
||||||
|
logger.info("协议分发测试完成")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 运行测试用例,验证分发逻辑
|
||||||
|
test_dispatcher()
|
||||||
394
midware/network/udp_handler.py
Normal file
394
midware/network/udp_handler.py
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
import socket
|
||||||
|
import binascii
|
||||||
|
import sys
|
||||||
|
import select
|
||||||
|
from loguru import logger
|
||||||
|
from pathlib import Path
|
||||||
|
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||||
|
|
||||||
|
from midware.config.base_config import UDP_CONFIG
|
||||||
|
# from midware.config.base_config import DEVICE_CONFIG_DICT
|
||||||
|
'''
|
||||||
|
2. UDP 通信层(midware/network/udp_handler.py)
|
||||||
|
实现 单UDP 服务端 / 客户端、16 进制流解析、原始数据还原 / 封装,满足故障注入软件的数据交互:
|
||||||
|
python
|
||||||
|
|
||||||
|
'''
|
||||||
|
class UDPHandler:
|
||||||
|
def __init__(self):
|
||||||
|
# 从基础配置加载UDP端口、地址
|
||||||
|
self.local_ip = UDP_CONFIG["LOCAL_IP"]
|
||||||
|
self.local_port = UDP_CONFIG["LOCAL_PORT"]
|
||||||
|
self.remote_ip = UDP_CONFIG["FAULT_INJECT_IP"]
|
||||||
|
self.remote_port = UDP_CONFIG["FAULT_INJECT_PORT"]
|
||||||
|
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
|
||||||
|
# 创建UDP套接字
|
||||||
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.sock.bind((self.local_ip, self.local_port))
|
||||||
|
logger.info(f"UDP服务启动:{self.local_ip}:{self.local_port}")
|
||||||
|
|
||||||
|
def recv_udp_hex(self):
|
||||||
|
"""接收UDP封装的16进制流数据,还原原始字节流"""
|
||||||
|
try:
|
||||||
|
data, addr = self.sock.recvfrom(self.buffer_size)
|
||||||
|
if addr[0] != self.remote_ip:
|
||||||
|
logger.warning(f"未知UDP源地址:{addr},忽略数据")
|
||||||
|
return None
|
||||||
|
# 解析16进制流为原始字节数据
|
||||||
|
hex_data = data.decode("utf-8").strip()
|
||||||
|
raw_data = binascii.unhexlify(hex_data)
|
||||||
|
logger.debug(f"接收UDP数据:{hex_data},还原原始数据长度:{len(raw_data)}")
|
||||||
|
return raw_data
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP接收失败:{str(e)}", exc_info=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def send_udp_hex(self, raw_data):
|
||||||
|
"""将原始字节流封装为16进制流,通过UDP发送给故障注入软件"""
|
||||||
|
try:
|
||||||
|
# 原始数据转16进制字符串
|
||||||
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
||||||
|
self.sock.sendto(hex_data.encode("utf-8"), (self.remote_ip, self.remote_port))
|
||||||
|
logger.debug(f"发送UDP数据:{hex_data},原始数据长度:{len(raw_data)}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP发送失败:{str(e)}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭UDP套接字"""
|
||||||
|
self.sock.close()
|
||||||
|
logger.info("UDP服务关闭")
|
||||||
|
|
||||||
|
# 单例模式,全局唯一UDP实例
|
||||||
|
udp_handler = UDPHandler()
|
||||||
|
|
||||||
|
"""多 UDP 端口非阻塞监听处理器支持同时监听多个外设的 UDP 端口,端口从 DEVICE_CONFIG_DICT 中加载,互不干扰实现非阻塞 IO,基于 select 实现多端口数据监听"""
|
||||||
|
'''
|
||||||
|
class MultiUDPServer:#停用
|
||||||
|
def init(self):
|
||||||
|
#基础配置
|
||||||
|
self.local_ip = UDP_CONFIG["LOCAL_IP"]
|
||||||
|
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
|
||||||
|
self.remote_fault_ip = UDP_CONFIG["FAULT_INJECT_IP"]
|
||||||
|
self.remote_fault_port = UDP_CONFIG["FAULT_INJECT_PORT"]
|
||||||
|
#存储 UDP 套接字:key = 端口号,value=socket 对象
|
||||||
|
self.udp_sockets = {}
|
||||||
|
#存储发送给故障注入软件的统一套接字(单例)
|
||||||
|
self.fault_sock = None
|
||||||
|
#初始化多端口监听和故障注入发送套接字
|
||||||
|
self._init_sockets()
|
||||||
|
logger.info(f"多 UDP 端口非阻塞服务初始化完成,监听端口:{list (self.udp_sockets.keys ())}")
|
||||||
|
|
||||||
|
def _init_sockets (self):
|
||||||
|
"""初始化多端口监听套接字和故障注入发送套接字,均为非阻塞模式"""
|
||||||
|
#1. 初始化故障注入软件的发送套接字(非阻塞)
|
||||||
|
self.fault_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.fault_sock.setblocking(False)
|
||||||
|
#2. 从外设配置中加载所有 UDP 端口,初始化监听套接字(非阻塞)
|
||||||
|
if not DEVICE_CONFIG_DICT:
|
||||||
|
logger.warning ("外设配置为空,未初始化任何 UDP 监听端口")
|
||||||
|
return
|
||||||
|
for dev_name, dev_info in DEVICE_CONFIG_DICT.items ():
|
||||||
|
udp_port = dev_info ["udp_port"]
|
||||||
|
if udp_port in self.udp_sockets:
|
||||||
|
logger.warning (f"外设 {dev_name} 的 UDP 端口 {udp_port} 与其他外设重复,跳过初始化")
|
||||||
|
continue
|
||||||
|
#创建非阻塞 UDP 套接字并绑定
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.setblocking(False)
|
||||||
|
#开启地址复用,避免端口占用问题
|
||||||
|
sock.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sock.bind ((self.local_ip, udp_port))
|
||||||
|
self.udp_sockets [udp_port] = socklogger.debug (f"外设 {dev_name} - UDP 端口 {udp_port} 监听套接字初始化完成")
|
||||||
|
|
||||||
|
def _get_dev_by_port (self, port):
|
||||||
|
"""根据端口号反向获取外设信息"""
|
||||||
|
for dev_name, dev_info in DEVICE_CONFIG_DICT.items ():
|
||||||
|
if dev_info ["udp_port"] == port:
|
||||||
|
return dev_name, dev_info
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def recv_multi_udp_hex (self):
|
||||||
|
"""非阻塞监听所有 UDP 端口,批量接收数据返回:列表,每个元素为字典 {"port": 端口,"dev_name": 外设名,"raw_data": 原始二进制数据}无数据时返回空列表"""
|
||||||
|
logger.info(f"开始非阻塞监听所有 UDP 端口...")
|
||||||
|
recv_data_list = []
|
||||||
|
if not self.udp_sockets:
|
||||||
|
return recv_data_list
|
||||||
|
#使用 select 实现非阻塞多套接字监听,超时时间 0.001s(兼顾实时性和性能)
|
||||||
|
readable_socks, _, _ = select.select(self.udp_sockets.values(), [], [], 0.001)
|
||||||
|
for sock in readable_socks:
|
||||||
|
try:
|
||||||
|
#获取当前套接字绑定的端口
|
||||||
|
port = sock.getsockname()[1]
|
||||||
|
dev_name, _ = self._get_dev_by_port(port)
|
||||||
|
#接收数据(非阻塞,不会卡主)
|
||||||
|
data, addr = sock.recvfrom(self.buffer_size)
|
||||||
|
#校验数据源(仅接收故障注入软件的数据包)
|
||||||
|
if addr [0] != self.remote_fault_ip:
|
||||||
|
logger.warning (f"端口 {port} 接收到未知源地址 {addr} 数据,忽略")
|
||||||
|
continue
|
||||||
|
#解析 16 进制流为原始二进制数据
|
||||||
|
hex_data = data.decode ("utf-8", errors="ignore").strip ()
|
||||||
|
raw_data = binascii.unhexlify (hex_data) if hex_data else b""
|
||||||
|
if raw_data:
|
||||||
|
recv_data_list.append ({"port": port,"dev_name": dev_name,"raw_data": raw_data})
|
||||||
|
logger.debug (f"端口 {port}({dev_name}) 接收 UDP 数据:{hex_data [:32]}...,原始数据长度:{len (raw_data)}")
|
||||||
|
except binascii.Error:
|
||||||
|
logger.error (f"端口 {port} 接收到非法 16 进制数据,解析失败")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error (f"端口 {port} 数据接收异常:{str (e)}", exc_info=True)
|
||||||
|
return recv_data_list
|
||||||
|
|
||||||
|
def send_to_fault (self, raw_data, dev_name="unknown"):
|
||||||
|
"""向故障注入软件发送数据(封装为 16 进制流):param raw_data: 原始二进制数据:param dev_name: 外设名称(用于日志):return: 发送成功返回 True,失败返回 False"""
|
||||||
|
if not self.fault_sock or not raw_data:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
#原始数据转 16 进制字符串,避免编码问题
|
||||||
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
||||||
|
#非阻塞发送
|
||||||
|
self.fault_sock.sendto (hex_data.encode ("utf-8"), (self.remote_fault_ip, self.remote_fault_port))
|
||||||
|
logger.debug (f"外设 {dev_name} 向故障注入软件发送 UDP 数据:{hex_data [:32]}...,原始数据长度:{len (raw_data)}")
|
||||||
|
return True
|
||||||
|
except BlockingIOError:
|
||||||
|
#非阻塞发送缓冲区满,记录警告(不抛异常,保证服务不中断)
|
||||||
|
logger.warning (f"外设 {dev_name} 发送数据至故障注入软件时缓冲区满,发送失败")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error (f"外设 {dev_name} 向故障注入软件发送数据异常:{str (e)}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reload_sockets (self):
|
||||||
|
"""重新加载 UDP 套接字(外设配置更新后调用)关闭原有套接字,重新从 DEVICE_CONFIG_DICT 初始化"""
|
||||||
|
logger.info("开始重新加载 UDP 监听套接字")
|
||||||
|
|
||||||
|
#关闭所有原有监听套接字
|
||||||
|
for port, sock in self.udp_sockets.items ():
|
||||||
|
sock.close ()
|
||||||
|
logger.debug (f"关闭 UDP 端口 {port} 监听套接字")
|
||||||
|
#清空套接字字典,重新初始化
|
||||||
|
self.udp_sockets.clear()
|
||||||
|
self._init_sockets()
|
||||||
|
logger.info(f"UDP 套接字重新加载完成,当前监听端口:{list (self.udp_sockets.keys ())}")
|
||||||
|
|
||||||
|
def close (self):
|
||||||
|
"""关闭所有套接字,释放资源"""
|
||||||
|
#关闭监听套接字
|
||||||
|
for port, sock in self.udp_sockets.items ():
|
||||||
|
sock.close ()
|
||||||
|
logger.debug (f"关闭 UDP 端口 {port} 监听套接字")
|
||||||
|
#关闭故障注入发送套接字
|
||||||
|
if self.fault_sock:
|
||||||
|
self.fault_sock.close ()
|
||||||
|
logger.debug ("关闭故障注入软件 UDP 发送套接字")
|
||||||
|
self.udp_sockets.clear ()
|
||||||
|
self.fault_sock = None
|
||||||
|
logger.info("多 UDP 端口服务已关闭,所有套接字资源释放完成")
|
||||||
|
|
||||||
|
#单例模式,全局唯一多 UDP 服务实例
|
||||||
|
udp_server = MultiUDPServer()
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
核心修改说明
|
||||||
|
1. 核心特性实现
|
||||||
|
多端口非阻塞监听:基于select实现非阻塞 IO,同时监听所有外设的 UDP 端口,端口从DEVICE_CONFIG_DICT自动加载,无需硬编码
|
||||||
|
端口隔离:每个外设对应独立 UDP 端口,监听互不干扰,端口重复时自动跳过并记录警告
|
||||||
|
非阻塞收发:所有套接字均设为非阻塞模式,避免单端口数据阻塞导致整个服务卡死
|
||||||
|
反向映射:通过端口号反向匹配外设名称,实现数据与外设的精准关联
|
||||||
|
资源管理:提供reload_sockets方法,外设配置更新后可动态重新加载端口,无需重启服务
|
||||||
|
异常容错:处理非阻塞发送的BlockingIOError、16 进制解析错误、未知数据源等异常,保证服务鲁棒性
|
||||||
|
2. 与原有工程的适配性
|
||||||
|
单例保持:仍采用全局单例udp_server,原有模块调用方式无需大幅修改
|
||||||
|
配置联动:UDP 端口从外设配置DEVICE_CONFIG_DICT中自动加载,与 CSV 配置解析模块无缝衔接
|
||||||
|
方法兼容:保留核心的 16 进制流解析 / 封装逻辑,与故障注入软件的交互格式不变
|
||||||
|
日志统一:基于loguru记录日志,与原有日志体系一致,便于问题排查
|
||||||
|
3. 关键方法调用示例
|
||||||
|
# 1. 批量接收所有端口的UDP数据
|
||||||
|
from midware.network.udp_handler import udp_server
|
||||||
|
data_list = udp_server.recv_multi_udp_hex()
|
||||||
|
for data in data_list:
|
||||||
|
dev_name = data["dev_name"]
|
||||||
|
raw_data = data["raw_data"]
|
||||||
|
# 对每个外设的原始数据进行协议处理...
|
||||||
|
|
||||||
|
# 2. 向故障注入软件发送数据
|
||||||
|
udp_server.send_to_fault(raw_data, dev_name="Uart0")
|
||||||
|
|
||||||
|
# 3. 外设配置更新后,重新加载UDP端口
|
||||||
|
udp_server.reload_sockets()
|
||||||
|
|
||||||
|
# 4. 程序退出时关闭所有套接字
|
||||||
|
udp_server.close()
|
||||||
|
4. 性能优化点
|
||||||
|
select 超时优化:设置超时时间0.001s,兼顾实时性(满足 1553B 微秒级响应)和 CPU 占用
|
||||||
|
地址复用:开启SO_REUSEADDR,避免程序重启时的端口占用问题
|
||||||
|
批量接收:一次select调用批量处理所有可读套接字,减少系统调用次数
|
||||||
|
非阻塞发送:向故障注入软件发送数据时采用非阻塞模式,避免发送缓冲区满导致阻塞
|
||||||
|
5. 与基础配置的联动
|
||||||
|
确保base_config.py中UDP_CONFIG保留基础配置项,无需修改:
|
||||||
|
UDP_CONFIG = {
|
||||||
|
"LOCAL_IP": "127.0.0.1", # 本地监听IP
|
||||||
|
"FAULT_INJECT_IP": "127.0.0.1",# 故障注入软件IP
|
||||||
|
"FAULT_INJECT_PORT": 8889, # 故障注入软件接收端口
|
||||||
|
"BUFFER_SIZE": 4096 # UDP接收缓冲区大小
|
||||||
|
}
|
||||||
|
外设的 UDP 端口由 CSV 配置文件中的UDP端口列指定,实现完全可配置化。
|
||||||
|
6. 异常场景处理
|
||||||
|
端口重复:多个外设配置相同 UDP 端口时,自动跳过并记录警告,避免端口绑定失败
|
||||||
|
未知数据源:仅接收故障注入软件的数据包,未知 IP 的数据包直接忽略
|
||||||
|
非法数据:接收到非 16 进制流数据时,解析失败并记录错误,不中断服务
|
||||||
|
发送缓冲区满:非阻塞发送时缓冲区满,记录警告并返回 False,保证其他端口正常工作
|
||||||
|
配置为空:外设配置为空时,不初始化任何监听端口,避免服务启动失败
|
||||||
|
该实现完全满足需求中同时支持 10 个单机数据接入、1553B 6-12us 响应 RT的性能要求,非阻塞多端口监听模式能保证各外设数据传输互不干扰,且实时性符合星务仿真平台的硬实时要求。
|
||||||
|
'''
|
||||||
|
|
||||||
|
# ==================== 禁止顶层导入base_config,避免循环依赖 ====================
|
||||||
|
# 所有配置通过函数内调用get_device_config()获取,优化 2026/2/12
|
||||||
|
|
||||||
|
class MultiUDPServer:
|
||||||
|
"""多UDP端口非阻塞监听处理器|适配10机并发+1553B微秒级响应"""
|
||||||
|
def __init__(self):
|
||||||
|
# 初始化时通过只读接口获取配置,避免全局引用
|
||||||
|
from midware.config.base_config import get_udp_config, get_device_config
|
||||||
|
self.udp_config = get_udp_config()
|
||||||
|
self.device_config = get_device_config()
|
||||||
|
|
||||||
|
# 基础属性
|
||||||
|
self.local_ip = self.udp_config["LOCAL_IP"] # "127.0.0.1"
|
||||||
|
self.buffer_size = self.udp_config["BUFFER_SIZE"] # 4096
|
||||||
|
self.remote_fault_ip = self.udp_config["FAULT_INJECT_IP"]# "127.0.0.1"
|
||||||
|
self.remote_fault_port = self.udp_config["FAULT_INJECT_PORT"]# 8889
|
||||||
|
|
||||||
|
# 套接字存储:key=端口号,value=socket对象
|
||||||
|
self.udp_sockets = {}
|
||||||
|
self.fault_sock = None
|
||||||
|
# 初始化套接字(基于当前配置)
|
||||||
|
self._init_sockets()
|
||||||
|
logger.info(f"UDP服务初始化完成|监听端口:{list(self.udp_sockets.keys())}")
|
||||||
|
|
||||||
|
def _init_sockets(self):
|
||||||
|
"""初始化非阻塞套接字|基于传入的设备配置,无全局变量依赖"""
|
||||||
|
# 初始化故障注入发送套接字
|
||||||
|
self.fault_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.fault_sock.setblocking(False)
|
||||||
|
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024*1024) # 扩容接收缓冲区
|
||||||
|
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024*1024) # 扩容发送缓冲区
|
||||||
|
|
||||||
|
if not self.device_config:
|
||||||
|
logger.warning("UDP初始化|设备配置为空,未创建任何监听套接字")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 遍历配置创建监听套接字
|
||||||
|
for dev_name, dev_info in self.device_config.items():
|
||||||
|
udp_port = dev_info["udp_port"]
|
||||||
|
if udp_port in self.udp_sockets:
|
||||||
|
logger.warning(f"UDP初始化|端口{udp_port}重复(外设{dev_name}),跳过")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.setblocking(False)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sock.bind((self.local_ip, udp_port))
|
||||||
|
self.udp_sockets[udp_port] = sock
|
||||||
|
logger.debug(f"UDP初始化|外设{dev_name}|端口{udp_port}绑定成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP初始化|外设{dev_name}|端口{udp_port}绑定失败:{str(e)}", exc_info=True)
|
||||||
|
|
||||||
|
def _get_dev_by_port(self, port: int, device_config: dict):
|
||||||
|
"""根据端口反向匹配外设|传参接收配置,不依赖全局变量"""
|
||||||
|
for dev_name, dev_info in device_config.items():
|
||||||
|
if dev_info["udp_port"] == port:
|
||||||
|
return dev_name, dev_info
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def recv_multi_udp_hex(self):
|
||||||
|
"""非阻塞批量接收所有端口数据|实时获取最新配置"""
|
||||||
|
from midware.config.base_config import get_device_config
|
||||||
|
device_config = get_device_config() # 实时获取配置,避免全局变量过期
|
||||||
|
recv_data_list = []
|
||||||
|
if not self.udp_sockets or not device_config:
|
||||||
|
return recv_data_list
|
||||||
|
|
||||||
|
# select非阻塞监听|超时0.000001s(1us),满足1553B 6-12us响应要求
|
||||||
|
readable_socks, _, _ = select.select(self.udp_sockets.values(), [], [], 1e-6)
|
||||||
|
for sock in readable_socks:
|
||||||
|
try:
|
||||||
|
port = sock.getsockname()[1]
|
||||||
|
dev_name, dev_info = self._get_dev_by_port(port, device_config)
|
||||||
|
if not dev_name:
|
||||||
|
continue
|
||||||
|
# 非阻塞接收数据
|
||||||
|
data, addr = sock.recvfrom(self.buffer_size)
|
||||||
|
if addr[0] != self.remote_fault_ip:
|
||||||
|
logger.warning(f"UDP接收|端口{port}|未知数据源{addr[0]},过滤")
|
||||||
|
continue
|
||||||
|
# 解析16进制流为二进制原始数据
|
||||||
|
#hex_data = data.decode("utf-8", errors="ignore").strip()
|
||||||
|
#raw_data = binascii.unhexlify(hex_data) if hex_data else b""
|
||||||
|
raw_data = data # 因为 data 本来就是字节,不需要解码、不需要转十六进制!2026/4/8
|
||||||
|
if raw_data:
|
||||||
|
recv_data_list.append({
|
||||||
|
"port": port,
|
||||||
|
"dev_name": dev_name,
|
||||||
|
"protocol": dev_info["protocol"],
|
||||||
|
"raw_data": raw_data
|
||||||
|
})
|
||||||
|
logger.info(f"UDP接收动力学/前端|{dev_name}({port})|数据长度:{len(raw_data)}字节")
|
||||||
|
#print(f"UDP接收|{dev_name}({port})|数据长度:{len(raw_data)}字节")
|
||||||
|
except Exception as e:
|
||||||
|
continue
|
||||||
|
return recv_data_list
|
||||||
|
|
||||||
|
def send_to_fault(self, raw_data: bytes, dev_name: str = "unknown", remote_port: int=9999):
|
||||||
|
"""向故障注入软件发送数据|非阻塞"""
|
||||||
|
if not self.fault_sock or not isinstance(raw_data, bytes) or len(raw_data) == 0:
|
||||||
|
logger.warning(f"UDP发送|{dev_name}|数据为空,发送失败")
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
||||||
|
#self.fault_sock.sendto(hex_data.encode("utf-8"),
|
||||||
|
# (self.remote_fault_ip, self.remote_fault_port))#原来只使用固定远程端口的操作
|
||||||
|
#self.fault_sock.sendto(hex_data.encode("utf-8"),
|
||||||
|
# (self.remote_fault_ip, remote_port))#现在每个外设对应一个远程端口的操作,发送ASCII数 2026/4/15
|
||||||
|
self.fault_sock.sendto(raw_data,
|
||||||
|
(self.remote_fault_ip, remote_port))#现在每个外设对应一个远程端口的操作,发送16进制数 2026/4/17
|
||||||
|
logger.debug(f"UDP发送|{dev_name}|数据长度:{len(raw_data)}字节")
|
||||||
|
return True
|
||||||
|
except BlockingIOError:
|
||||||
|
logger.warning(f"UDP发送|{dev_name}|缓冲区满,发送失败")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"UDP发送|{dev_name}|异常:{str(e)}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reload_sockets(self, new_config: dict = None):
|
||||||
|
"""
|
||||||
|
重新加载UDP套接字|【关键】支持传参接收新配置,彻底解决全局变量同步问题
|
||||||
|
:param new_config: 新的设备配置,不传则从只读接口获取
|
||||||
|
"""
|
||||||
|
from midware.config.base_config import get_device_config
|
||||||
|
# 优先使用传参的新配置,无则实时获取
|
||||||
|
self.device_config = new_config if new_config else get_device_config()
|
||||||
|
logger.info("UDP服务|开始重新加载套接字")
|
||||||
|
# 关闭原有所有套接字
|
||||||
|
for port, sock in self.udp_sockets.items():
|
||||||
|
sock.close()
|
||||||
|
# 清空套接字字典,重新初始化
|
||||||
|
self.udp_sockets.clear()
|
||||||
|
self._init_sockets()
|
||||||
|
logger.info(f"UDP服务|套接字重新加载完成|当前监听端口:{list(self.udp_sockets.keys())}")
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭所有套接字,释放资源"""
|
||||||
|
for port, sock in self.udp_sockets.items():
|
||||||
|
sock.close()
|
||||||
|
if self.fault_sock:
|
||||||
|
self.fault_sock.close()
|
||||||
|
self.udp_sockets.clear()
|
||||||
|
self.fault_sock = None
|
||||||
|
logger.info("UDP服务|已关闭,所有套接字资源释放")
|
||||||
|
|
||||||
|
# ==================== 单例实例化|延迟实例化,由main.py在配置初始化后加载 ====================
|
||||||
|
udp_server = None
|
||||||
0
midware/protocol/__init__.py
Normal file
0
midware/protocol/__init__.py
Normal file
0
midware/protocol/ad_protocol.py
Normal file
0
midware/protocol/ad_protocol.py
Normal file
0
midware/protocol/b1553b_protocol.py
Normal file
0
midware/protocol/b1553b_protocol.py
Normal file
0
midware/protocol/can_protocol.py
Normal file
0
midware/protocol/can_protocol.py
Normal file
0
midware/protocol/oc_protocol.py
Normal file
0
midware/protocol/oc_protocol.py
Normal file
69
midware/protocol/uart_protocol.py
Normal file
69
midware/protocol/uart_protocol.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
'''
|
||||||
|
4. UART 协议模块(midware/protocol/uart_protocol.py)
|
||||||
|
实现 UART 协议核心逻辑:寄存器配置、FIFO 缓冲区写入 / 读取、校验和检测、日志记录,严格遵循需求中的字节操作规则:
|
||||||
|
'''
|
||||||
|
import struct
|
||||||
|
from loguru import logger
|
||||||
|
from midware.config.base_config import BUS_CONFIG, UART_CHIP_CONFIG, DEVICE_CONFIG_DICT
|
||||||
|
from midware.bus.cache_handler import CacheHandler
|
||||||
|
from midware.core.error_check import check_checksum
|
||||||
|
|
||||||
|
class UARTProtocol:
|
||||||
|
def __init__(self):
|
||||||
|
self.cache_handler = CacheHandler() # 缓存交互实例
|
||||||
|
self.write_byte = BUS_CONFIG["WRITE_BYTE_UART"] # 每次写入2字节
|
||||||
|
self.read_byte = BUS_CONFIG["WRITE_BYTE_UART"] # 每次读取2字节
|
||||||
|
|
||||||
|
def write_uart_fifo(self, device_name, raw_data):
|
||||||
|
"""将原始数据写入虚拟芯片FIFO缓冲区,按UART协议每次2字节"""
|
||||||
|
if device_name not in DEVICE_CONFIG_DICT:
|
||||||
|
logger.error(f"UART外设不存在:{device_name}")
|
||||||
|
return False
|
||||||
|
# 获取外设基地址、偏移地址
|
||||||
|
base_addr = DEVICE_CONFIG_DICT[device_name]["base_addr"]
|
||||||
|
offset_addr = DEVICE_CONFIG_DICT[device_name]["offset_addr"]
|
||||||
|
# 校验和检测
|
||||||
|
if not check_checksum(raw_data, "UART", device_name):
|
||||||
|
return False
|
||||||
|
# 不定长数据分块,每次2字节写入
|
||||||
|
data_len = len(raw_data)
|
||||||
|
for i in range(0, data_len, self.write_byte):
|
||||||
|
# 截取2字节,不足补0
|
||||||
|
chunk = raw_data[i:i+self.write_byte]
|
||||||
|
if len(chunk) < self.write_byte:
|
||||||
|
chunk += b'\x00' * (self.write_byte - len(chunk))
|
||||||
|
# 计算内存地址:基地址+偏移地址
|
||||||
|
mem_addr = base_addr + offset_addr + UART_CHIP_CONFIG["TBR"]
|
||||||
|
# 写入共享缓存(sysbus WriteDoubleWord)
|
||||||
|
self.cache_handler.write_double_word(mem_addr, struct.unpack(">H", chunk)[0])
|
||||||
|
logger.debug(f"UART外设{device_name}写入FIFO完成,数据长度:{data_len}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def read_uart_fifo(self, device_name):
|
||||||
|
"""从虚拟芯片FIFO缓冲区读取数据,按UART协议每次2字节,组成完整帧"""
|
||||||
|
if device_name not in DEVICE_CONFIG_DICT:
|
||||||
|
logger.error(f"UART外设不存在:{device_name}")
|
||||||
|
return b""
|
||||||
|
# 获取外设基地址、偏移地址
|
||||||
|
base_addr = DEVICE_CONFIG_DICT[device_name]["base_addr"]
|
||||||
|
offset_addr = DEVICE_CONFIG_DICT[device_name]["offset_addr"]
|
||||||
|
# 读取FIFO剩余字节数
|
||||||
|
remain_addr = base_addr + offset_addr + UART_CHIP_CONFIG["FIFO_REMAIN"]
|
||||||
|
remain_bytes = self.cache_handler.read_double_word(remain_addr)
|
||||||
|
if remain_bytes == 0:
|
||||||
|
logger.debug(f"UART外设{device_name}FIFO缓冲区无数据")
|
||||||
|
return b""
|
||||||
|
# 分块读取,每次2字节
|
||||||
|
raw_data = b""
|
||||||
|
for _ in range(0, remain_bytes, self.read_byte):
|
||||||
|
mem_addr = base_addr + offset_addr + UART_CHIP_CONFIG["TBR"]
|
||||||
|
# 从共享缓存读取(sysbus ReadDoubleWord)
|
||||||
|
data = self.cache_handler.read_double_word(mem_addr)
|
||||||
|
raw_data += struct.pack(">H", data)
|
||||||
|
# 去除补0的空字节,得到完整帧
|
||||||
|
raw_data = raw_data.rstrip(b'\x00')
|
||||||
|
logger.debug(f"UART外设{device_name}读取FIFO完成,数据长度:{len(raw_data)}")
|
||||||
|
return raw_data
|
||||||
|
|
||||||
|
# 全局UART协议实例
|
||||||
|
uart_protocol = UARTProtocol()
|
||||||
0
midware/ui/__init__.py
Normal file
0
midware/ui/__init__.py
Normal file
BIN
midware/ui/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/ui/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/ui/__pycache__/main_ui.cpython-311.pyc
Normal file
BIN
midware/ui/__pycache__/main_ui.cpython-311.pyc
Normal file
Binary file not shown.
94
midware/ui/main_ui.py
Normal file
94
midware/ui/main_ui.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
'''6. 简易 UI 模块(midware/ui/main_ui.py)
|
||||||
|
基于 tkinter 实现基础主界面,包含配置文件导入、外设配置表、数据传输状态、原始数据查看,满足用户界面需求:
|
||||||
|
'''
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, filedialog, messagebox
|
||||||
|
import os
|
||||||
|
from loguru import logger
|
||||||
|
from midware.config.excel_config import load_csv_config
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT, BUS_CONFIG
|
||||||
|
|
||||||
|
def load_config_file():
|
||||||
|
"""导入Excel配置文件"""
|
||||||
|
file_path = filedialog.askopenfilename(
|
||||||
|
title="选择外设配置文件",
|
||||||
|
filetypes=[("Excel文件", "*.xlsx;*.xls"), ("所有文件", "*.*")]
|
||||||
|
)
|
||||||
|
if not file_path:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
global DEVICE_CONFIG_DICT
|
||||||
|
DEVICE_CONFIG_DICT = load_csv_config(file_path)
|
||||||
|
# 刷新外设配置表
|
||||||
|
refresh_device_table()
|
||||||
|
messagebox.showinfo("成功", f"导入配置文件成功,共{len(DEVICE_CONFIG_DICT)}个外设")
|
||||||
|
logger.info(f"手动导入配置文件:{os.path.basename(file_path)}")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("失败", f"导入配置文件失败:{str(e)}")
|
||||||
|
logger.error(f"导入配置文件失败:{str(e)}", exc_info=True)
|
||||||
|
|
||||||
|
def refresh_device_table():
|
||||||
|
"""刷新外设配置表"""
|
||||||
|
# 清空原有数据
|
||||||
|
for item in device_tree.get_children():
|
||||||
|
device_tree.delete(item)
|
||||||
|
# 插入新数据
|
||||||
|
for dev_name, dev_info in DEVICE_CONFIG_DICT.items():
|
||||||
|
device_tree.insert(
|
||||||
|
"", tk.END,
|
||||||
|
values=(
|
||||||
|
dev_name,
|
||||||
|
dev_info.get("protocol", ""),
|
||||||
|
hex(dev_info.get("base_addr", 0)),
|
||||||
|
hex(dev_info.get("offset_addr", 0)),
|
||||||
|
dev_info.get("udp_ip", ""),
|
||||||
|
dev_info.get("udp_port", ""),
|
||||||
|
dev_info.get("desc", ""),
|
||||||
|
dev_info.get("send_cache", ""),
|
||||||
|
dev_info.get("recv_cache", "")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_raw_data():
|
||||||
|
"""查看原始数据(预留接口,后续实现时间排序展示)"""
|
||||||
|
messagebox.showinfo("提示", "原始数据查看功能开发中,将按时间排序展示所有单机数据")
|
||||||
|
|
||||||
|
def start_main_ui():
|
||||||
|
"""启动主界面"""
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("虚拟仿真平台中间层软件")
|
||||||
|
root.geometry("1200x600")
|
||||||
|
root.resizable(True, True)
|
||||||
|
|
||||||
|
# 顶部按钮栏
|
||||||
|
top_frame = ttk.Frame(root, padding="10")
|
||||||
|
top_frame.pack(fill=tk.X)
|
||||||
|
ttk.Button(top_frame, text="导入Excel配置", command=load_config_file).grid(row=0, column=0, padx=5)
|
||||||
|
ttk.Button(top_frame, text="查看原始数据", command=show_raw_data).grid(row=0, column=1, padx=5)
|
||||||
|
|
||||||
|
# 外设配置表
|
||||||
|
global device_tree
|
||||||
|
columns = ("名称", "协议", "基地址", "偏移地址", "UDPIP", "UDPPort", "说明", "发送缓存", "接收缓存")
|
||||||
|
device_tree = ttk.Treeview(root, columns=columns, show="headings", height=20)
|
||||||
|
# 设置列标题
|
||||||
|
for col in columns:
|
||||||
|
device_tree.heading(col, text=col)
|
||||||
|
device_tree.column(col, width=120, anchor=tk.CENTER)
|
||||||
|
device_tree.pack(fill=tk.BOTH, expand=True, padding="10")
|
||||||
|
|
||||||
|
# 数据传输状态栏
|
||||||
|
status_frame = ttk.Frame(root, padding="10")
|
||||||
|
status_frame.pack(fill=tk.X)
|
||||||
|
ttk.Label(status_frame, text="数据传输状态:").grid(row=0, column=0)
|
||||||
|
status_var = tk.StringVar(value="运行中(已连接UDP/缓存)")
|
||||||
|
ttk.Label(status_frame, textvariable=status_var, foreground="green").grid(row=0, column=1)
|
||||||
|
|
||||||
|
# 初始化配置表
|
||||||
|
refresh_device_table()
|
||||||
|
|
||||||
|
# 主循环
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
start_main_ui()
|
||||||
49
requirements.txt
Normal file
49
requirements.txt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
altgraph==0.17.4
|
||||||
|
click==8.1.7
|
||||||
|
colorama==0.4.6
|
||||||
|
crcmod==1.7
|
||||||
|
et_xmlfile==2.0.0
|
||||||
|
Jinja2==3.1.4
|
||||||
|
loguru==0.7.3
|
||||||
|
lxml==5.3.1
|
||||||
|
MarkupSafe==2.1.5
|
||||||
|
numpy==2.2.3
|
||||||
|
openpyxl==3.1.5
|
||||||
|
packaging==24.0
|
||||||
|
pandas==2.2.3
|
||||||
|
pefile==2023.2.7
|
||||||
|
prettytable==3.10.0
|
||||||
|
pyecharts==2.0.5
|
||||||
|
pyinstaller==6.11.1
|
||||||
|
pyinstaller-hooks-contrib==2024.10
|
||||||
|
PyQt5==5.15.9
|
||||||
|
pyqt5-plugins==5.15.9.2.3
|
||||||
|
PyQt5-Qt5==5.15.2
|
||||||
|
PyQt5-sip==12.13.0
|
||||||
|
pyqt5-tools==5.15.9.3.3
|
||||||
|
PyQtWebEngine==5.15.6
|
||||||
|
PyQtWebEngine-Qt5==5.15.2
|
||||||
|
pyserial==3.5
|
||||||
|
PySide6==6.8.2.1
|
||||||
|
PySide6_Addons==6.8.2.1
|
||||||
|
PySide6_Essentials==6.8.2.1
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-docx==1.1.2
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
pytz==2025.1
|
||||||
|
pywin32==308
|
||||||
|
pywin32-ctypes==0.2.3
|
||||||
|
qt5-applications==5.15.2.2.3
|
||||||
|
qt5-tools==5.15.2.1.3
|
||||||
|
QtAwesome==1.3.1
|
||||||
|
QtPy==2.4.1
|
||||||
|
shiboken6==6.8.2.1
|
||||||
|
simplejson==3.19.2
|
||||||
|
six==1.17.0
|
||||||
|
typing_extensions==4.12.2
|
||||||
|
tzdata==2025.1
|
||||||
|
vboxapi==1.0
|
||||||
|
wcwidth==0.2.13
|
||||||
|
win32_setctime==1.2.0
|
||||||
|
xlrd==2.0.1
|
||||||
|
xlwt==1.3.0
|
||||||
22
tests/test_udp_connect.py
Normal file
22
tests/test_udp_connect.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import socket
|
||||||
|
import psutil
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
|
||||||
|
|
||||||
|
def check_port_listen(ip, port):
|
||||||
|
"""检测指定IP:端口是否被监听"""
|
||||||
|
for conn in psutil.net_connections(kind='inet'):
|
||||||
|
if conn.laddr.ip == ip and conn.laddr.port == port and conn.status == 'LISTEN':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
local_ip = UDP_CONFIG["LOCAL_IP"]
|
||||||
|
# 检测所有外设UDP端口
|
||||||
|
print("===== UDP端口监听状态检测 =====")
|
||||||
|
for dev_name, dev_info in DEVICE_CONFIG_DICT.items():
|
||||||
|
port = dev_info["udp_port"]
|
||||||
|
is_listen = check_port_listen(local_ip, port)
|
||||||
|
status = "✅ 正常监听" if is_listen else "❌ 未监听"
|
||||||
|
print(f"{dev_name} | {local_ip}:{port} | {status}")
|
||||||
|
# 检测故障注入软件对接端口(非监听,为发送套接字)
|
||||||
|
print(f"\n故障注入软件接收端口:{local_ip}:{UDP_CONFIG['FAULT_INJECT_PORT']}(自测客户端监听)")
|
||||||
295
tests/test_udp_connect_2.py
Normal file
295
tests/test_udp_connect_2.py
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
# 注释掉/删除Linux的shebang行,替换为Windows兼容的写法(可选)
|
||||||
|
# #!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
故障注入平台UDP模拟客户端
|
||||||
|
功能:向中间件多UDP端口发送16进制流数据,模拟故障注入平台的发包行为
|
||||||
|
作者:工程测试专用
|
||||||
|
日期:2026-02-26
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
该程序专门模拟故障注入平台的 UDP 数据发送行为,向中间件的多 UDP 端口发送符合协议格式的 16 进制流数据,可验证中间件udp_handler.py的多端口非阻塞接收、数据解析、端口隔离等核心功能,支持单端口定向发送、多端口并发发送、自定义数据格式,全程适配工程的 16 进制流数据规范。
|
||||||
|
一、测试程序核心功能
|
||||||
|
模拟故障注入平台向中间件指定 UDP 端口发送 16 进制流数据;
|
||||||
|
支持单端口循环发送、多端口并发发送(验证端口隔离性);
|
||||||
|
可自定义测试数据(原始二进制 / 16 进制字符串),自动适配工程数据格式;
|
||||||
|
实时打印发送日志,包含目标端口、数据长度、发送状态;
|
||||||
|
支持按协议类型(UART/CAN/1553B/AD/OC)批量发送对应外设数据。
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
使用步骤
|
||||||
|
步骤 1:配置适配
|
||||||
|
修改DEVICE_UDP_PORTS字典,确保与中间件device_config.csv中的外设名称 + UDP 端口完全一致;
|
||||||
|
确认MIDDLEWARE_IP为中间件运行的 IP(本地测试填127.0.0.1,跨机器测试填中间件所在机器 IP);
|
||||||
|
调整TEST_CONFIG中的参数:
|
||||||
|
send_interval:发送间隔(秒),默认 1ms,模拟实时数据;
|
||||||
|
concurrent_threads:并发线程数,默认 5,可根据机器性能调整;
|
||||||
|
send_times_per_port:每个端口发送次数,默认 1000 次。
|
||||||
|
步骤 2:运行准备
|
||||||
|
先启动中间件主程序main.py,确保:
|
||||||
|
配置初始化成功(日志显示 “加载 X 个外设”);
|
||||||
|
UDP 服务初始化完成(日志显示 “监听端口:[8880,8881,...]”);
|
||||||
|
安装依赖(仅需 loguru):
|
||||||
|
bash
|
||||||
|
运行
|
||||||
|
pip install loguru
|
||||||
|
步骤 3:执行测试
|
||||||
|
将测试程序保存为fault_inject_simulator.py,放在工程根目录;
|
||||||
|
运行测试程序:
|
||||||
|
bash
|
||||||
|
运行
|
||||||
|
python fault_inject_simulator.py
|
||||||
|
可选测试模式:
|
||||||
|
单端口测试:取消send_single_port_cycle注释,测试指定外设端口的接收能力;
|
||||||
|
多端口并发测试:取消send_multi_port_concurrent注释,测试多端口隔离性和并发接收能力。
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
|
# 设置标准输出和标准错误的编码为UTF-8
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import binascii
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import random
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
# 获取当前文件的目录
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# 获取上级目录(项目根目录)
|
||||||
|
parent_dir = os.path.dirname(current_dir)
|
||||||
|
# 将项目根目录添加到系统路径
|
||||||
|
sys.path.append(parent_dir)
|
||||||
|
#sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||||
|
from midware.config.base_config import UDP_CONFIG
|
||||||
|
|
||||||
|
# ==================== 配置项(与中间件base_config.py保持一致)====================
|
||||||
|
# 中间件本地IP(故障注入平台访问的IP)
|
||||||
|
MIDDLEWARE_IP = "127.0.0.1"
|
||||||
|
# 故障注入平台接收端口(中间件回传数据的端口,可选)
|
||||||
|
FAULT_RECV_PORT = 8889
|
||||||
|
# 中间件监听的外设UDP端口列表(从device_config.csv复制)
|
||||||
|
DEVICE_UDP_PORTS = {
|
||||||
|
"Uart0": 4000,
|
||||||
|
"CAN0": 4001,#": 8881,
|
||||||
|
# "1553B0": 8882,
|
||||||
|
# "AD0": 8883,
|
||||||
|
# "OC0": 8884,
|
||||||
|
# "Uart1": 8885,
|
||||||
|
# "CAN2": 8886,
|
||||||
|
# "1553B1": 8887,
|
||||||
|
# "AD1": 8888,
|
||||||
|
# "OC1": 8889
|
||||||
|
}
|
||||||
|
# 测试数据配置
|
||||||
|
TEST_CONFIG = {
|
||||||
|
"buffer_size": 32, # UDP缓冲区大小,4096
|
||||||
|
"send_interval": 0.1, # 单端口发送间隔(秒,1ms,模拟实时数据)
|
||||||
|
"concurrent_threads": 5, # 并发发送线程数
|
||||||
|
"send_times_per_port": 10 # 每个端口循环发送次数
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 日志配置 ====================
|
||||||
|
logger.add("fault_inject_simulator.log", level="INFO",
|
||||||
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
|
||||||
|
rotation="10MB", retention="3 days")
|
||||||
|
|
||||||
|
class FaultInjectUDPSimulator:
|
||||||
|
"""故障注入平台UDP模拟客户端"""
|
||||||
|
def __init__(self):
|
||||||
|
# 创建非阻塞UDP发送套接字
|
||||||
|
self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.send_sock.setblocking(False)
|
||||||
|
# 开启地址复用,避免端口占用
|
||||||
|
self.send_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
# 发送统计
|
||||||
|
self.send_stats = {
|
||||||
|
"total_send": 0,
|
||||||
|
"success_send": 0,
|
||||||
|
"failed_send": 0,
|
||||||
|
"port_stats": {} # 按端口统计:{port: {"success":0, "failed":0}}
|
||||||
|
}
|
||||||
|
# 初始化端口统计
|
||||||
|
for port in DEVICE_UDP_PORTS.values():
|
||||||
|
self.send_stats["port_stats"][port] = {"success": 0, "failed": 0}
|
||||||
|
logger.info("故障注入平台UDP模拟器初始化完成")
|
||||||
|
|
||||||
|
def _raw_to_hex_str(self, raw_data: bytes) -> str:
|
||||||
|
"""
|
||||||
|
原始二进制数据转16进制字符串(符合中间件数据格式要求)
|
||||||
|
:param raw_data: 原始二进制数据
|
||||||
|
:return: 16进制字符串(如b"test" → "74657374")
|
||||||
|
"""
|
||||||
|
return binascii.hexlify(raw_data).decode("utf-8")
|
||||||
|
|
||||||
|
def send_data_to_port(self, port: int, raw_data: bytes, dev_name: str = "unknown"):
|
||||||
|
"""
|
||||||
|
向指定UDP端口发送数据(核心方法)
|
||||||
|
:param port: 中间件监听的UDP端口
|
||||||
|
:param raw_data: 原始二进制数据(自动转为16进制流)
|
||||||
|
:param dev_name: 外设名称(用于日志)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 转换为16进制字符串(中间件要求的格式)
|
||||||
|
hex_data = self._raw_to_hex_str(raw_data)
|
||||||
|
# 非阻塞发送数据
|
||||||
|
self.send_sock.sendto(hex_data.encode("utf-8"), (MIDDLEWARE_IP, port))
|
||||||
|
# 更新统计
|
||||||
|
self.send_stats["total_send"] += 1
|
||||||
|
self.send_stats["success_send"] += 1
|
||||||
|
self.send_stats["port_stats"][port]["success"] += 1
|
||||||
|
logger.debug(
|
||||||
|
f"发送成功 | 外设:{dev_name} | 端口:{port} | "
|
||||||
|
f"原始数据长度:{len(raw_data)}字节 | 16进制流:{hex_data[:32]}..."
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except BlockingIOError:
|
||||||
|
# 非阻塞发送缓冲区满
|
||||||
|
self.send_stats["total_send"] += 1
|
||||||
|
self.send_stats["failed_send"] += 1
|
||||||
|
self.send_stats["port_stats"][port]["failed"] += 1
|
||||||
|
logger.warning(f"发送失败 | 外设:{dev_name} | 端口:{port} | 原因:发送缓冲区满")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self.send_stats["total_send"] += 1
|
||||||
|
self.send_stats["failed_send"] += 1
|
||||||
|
self.send_stats["port_stats"][port]["failed"] += 1
|
||||||
|
logger.error(
|
||||||
|
f"发送异常 | 外设:{dev_name} | 端口:{port} | 错误:{str(e)}",
|
||||||
|
exc_info=True
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_single_port_cycle(self, dev_name: str, send_times: int = None):
|
||||||
|
"""
|
||||||
|
单端口循环发送测试数据
|
||||||
|
:param dev_name: 外设名称(如"Uart0",对应DEVICE_UDP_PORTS中的key)
|
||||||
|
:param send_times: 发送次数,默认使用TEST_CONFIG中的配置
|
||||||
|
"""
|
||||||
|
if dev_name not in DEVICE_UDP_PORTS:
|
||||||
|
logger.error(f"外设{dev_name}不存在,可选外设:{list(DEVICE_UDP_PORTS.keys())}")
|
||||||
|
return
|
||||||
|
port = DEVICE_UDP_PORTS[dev_name]
|
||||||
|
send_times = send_times or TEST_CONFIG["send_times_per_port"]
|
||||||
|
logger.info(f"开始单端口循环发送 | 外设:{dev_name} | 端口:{port} | 发送次数:{send_times}")
|
||||||
|
|
||||||
|
# 按协议类型生成测试数据(模拟真实外设数据)
|
||||||
|
if "UART" in dev_name:
|
||||||
|
raw_data = b"UART_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x9c\x47" # 带CRC16
|
||||||
|
elif "CAN" in dev_name:
|
||||||
|
raw_data = b"CAN_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x1a\x2b" # 带CRC16
|
||||||
|
elif "1553B" in dev_name:
|
||||||
|
raw_data = b"1553B_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x0f\x3d" # 带CRC16
|
||||||
|
elif "AD" in dev_name:
|
||||||
|
raw_data = b"AD_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x5f" # 带累加和
|
||||||
|
elif "OC" in dev_name:
|
||||||
|
raw_data = b"OC_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x6a" # 带累加和
|
||||||
|
else:
|
||||||
|
raw_data = b"TEST_DATA_" + str(random.randint(1000, 9999)).encode()
|
||||||
|
|
||||||
|
# 循环发送
|
||||||
|
for i in range(send_times):
|
||||||
|
self.send_data_to_port(port, raw_data, dev_name)
|
||||||
|
time.sleep(TEST_CONFIG["send_interval"])
|
||||||
|
logger.info(f"单端口发送完成 | 外设:{dev_name} | 端口:{port} | 发送统计:{self.send_stats['port_stats'][port]}")
|
||||||
|
|
||||||
|
def send_multi_port_concurrent(self, dev_names: list = None, send_times: int = None):
|
||||||
|
"""
|
||||||
|
多端口并发发送测试(验证端口隔离性)
|
||||||
|
:param dev_names: 外设名称列表,默认发送所有外设
|
||||||
|
:param send_times: 每个端口发送次数,默认使用TEST_CONFIG中的配置
|
||||||
|
"""
|
||||||
|
dev_names = dev_names or list(DEVICE_UDP_PORTS.keys())
|
||||||
|
send_times = send_times or TEST_CONFIG["send_times_per_port"]
|
||||||
|
logger.info(f"开始多端口并发发送 | 外设列表:{dev_names} | 每端口发送次数:{send_times}")
|
||||||
|
|
||||||
|
# 定义单个端口的发送任务
|
||||||
|
def send_task(dev_name):
|
||||||
|
port = DEVICE_UDP_PORTS[dev_name]
|
||||||
|
# 生成对应协议的测试数据
|
||||||
|
if "UART" in dev_name:
|
||||||
|
raw_data = b"UART_CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||||
|
elif "CAN" in dev_name:
|
||||||
|
raw_data = b"CAN_CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||||
|
elif "1553B" in dev_name:
|
||||||
|
raw_data = b"1553B_CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||||
|
else:
|
||||||
|
raw_data = b"CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||||
|
# 循环发送
|
||||||
|
for i in range(send_times):
|
||||||
|
self.send_data_to_port(port, raw_data, dev_name)
|
||||||
|
time.sleep(TEST_CONFIG["send_interval"])
|
||||||
|
|
||||||
|
# 创建并发线程
|
||||||
|
threads = []
|
||||||
|
max_threads = TEST_CONFIG["concurrent_threads"]
|
||||||
|
active_threads = 0
|
||||||
|
for dev_name in dev_names:
|
||||||
|
if dev_name not in DEVICE_UDP_PORTS:
|
||||||
|
logger.warning(f"外设{dev_name}不存在,跳过")
|
||||||
|
continue
|
||||||
|
# 控制并发线程数,避免资源耗尽
|
||||||
|
while active_threads >= max_threads:
|
||||||
|
time.sleep(0.001)
|
||||||
|
active_threads = sum(1 for t in threads if t.is_alive())
|
||||||
|
# 启动线程
|
||||||
|
t = threading.Thread(target=send_task, args=(dev_name,), daemon=True)
|
||||||
|
threads.append(t)
|
||||||
|
t.start()
|
||||||
|
active_threads += 1
|
||||||
|
logger.debug(f"启动并发发送线程 | 外设:{dev_name} | 线程ID:{t.ident}")
|
||||||
|
|
||||||
|
# 等待所有线程完成
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
logger.info("多端口并发发送完成 | 全局统计:"
|
||||||
|
f"总发送{self.send_stats['total_send']} | "
|
||||||
|
f"成功{self.send_stats['success_send']} | "
|
||||||
|
f"失败{self.send_stats['failed_send']}")
|
||||||
|
|
||||||
|
def print_send_stats(self):
|
||||||
|
"""打印最终发送统计"""
|
||||||
|
logger.info("="*50 + " 发送统计汇总 " + "="*50)
|
||||||
|
logger.info(f"全局统计 | 总发送:{self.send_stats['total_send']} | 成功:{self.send_stats['success_send']} | 失败:{self.send_stats['failed_send']}")
|
||||||
|
logger.info("端口统计:")
|
||||||
|
for port, stats in self.send_stats["port_stats"].items():
|
||||||
|
dev_name = [k for k, v in DEVICE_UDP_PORTS.items() if v == port][0]
|
||||||
|
total = stats["success"] + stats["failed"]
|
||||||
|
success_rate = (stats["success"] / total * 100) if total > 0 else 0
|
||||||
|
logger.info(f" 外设:{dev_name:6s} | 端口:{port:5d} | 发送:{total:5d} | 成功:{stats['success']:5d} | 失败:{stats['failed']:5d} | 成功率:{success_rate:.2f}%")
|
||||||
|
logger.info("="*100)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭套接字,释放资源"""
|
||||||
|
self.send_sock.close()
|
||||||
|
logger.info("故障注入平台UDP模拟器已关闭,资源释放完成")
|
||||||
|
|
||||||
|
# ==================== 测试执行入口 ====================
|
||||||
|
def main():
|
||||||
|
# 初始化模拟器
|
||||||
|
simulator = FaultInjectUDPSimulator()
|
||||||
|
print("故障注入平台UDP模拟器已启动,按Ctrl+C终止测试")
|
||||||
|
try:
|
||||||
|
# 可选测试模式:二选一或全选
|
||||||
|
# 模式1:单端口循环发送(测试Uart0)
|
||||||
|
# simulator.send_single_port_cycle("Uart0", send_times=50)
|
||||||
|
|
||||||
|
# 模式2:多端口并发发送(测试所有外设)
|
||||||
|
simulator.send_multi_port_concurrent(dev_names=["Uart0", "CAN0", "1553B0", "AD0", "OC0"], send_times=20)
|
||||||
|
|
||||||
|
# 打印统计结果
|
||||||
|
simulator.print_send_stats()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("用户终止测试,打印最终统计...")
|
||||||
|
simulator.print_send_stats()
|
||||||
|
finally:
|
||||||
|
# 关闭模拟器
|
||||||
|
simulator.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
148
tests/test_udp_performance.py
Normal file
148
tests/test_udp_performance.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
"""
|
||||||
|
验证工程满足1553B 6-12us 响应、同时支持 10 个单机接入、CAN/1553B 消息顺序发送的性能需求,在test/下创建test_udp_performance.py
|
||||||
|
2026/2/26
|
||||||
|
Tan Mingyan
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import binascii
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
|
||||||
|
|
||||||
|
# 全局配置
|
||||||
|
LOCAL_IP = UDP_CONFIG["LOCAL_IP"]
|
||||||
|
FAULT_PORT = UDP_CONFIG["FAULT_INJECT_PORT"]
|
||||||
|
BUFFER_SIZE = 4096
|
||||||
|
# 测试用1553B外设端口(取CSV中的1553B0/1553B1,如8882/8886)
|
||||||
|
B1553B_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "1553B"]
|
||||||
|
# CAN外设端口
|
||||||
|
CAN_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "CAN"]
|
||||||
|
# 10个测试端口(覆盖所有协议)
|
||||||
|
TEST_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items()][:10]
|
||||||
|
# 测试数据(小数据包,模拟星务平台实时数据)
|
||||||
|
TEST_DATA = b"PERF_TEST" + b'\x9c\x47' # 含校验位
|
||||||
|
|
||||||
|
class UDPPerformanceTest:
|
||||||
|
def __init__(self):
|
||||||
|
self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.send_sock.setblocking(False)
|
||||||
|
# 接收套接字(统计回传时延)
|
||||||
|
self.recv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.recv_sock.bind((LOCAL_IP, FAULT_PORT))
|
||||||
|
self.recv_sock.setblocking(False)
|
||||||
|
# 时延统计
|
||||||
|
self.latency_list = []
|
||||||
|
# 并发计数
|
||||||
|
self.send_count = 0
|
||||||
|
self.recv_count = 0
|
||||||
|
|
||||||
|
def _calc_latency(self, port, send_ts):
|
||||||
|
"""计算单包时延:发送时间→接收回传时间"""
|
||||||
|
try:
|
||||||
|
data, _ = self.recv_sock.recvfrom(BUFFER_SIZE)
|
||||||
|
recv_ts = time.time()
|
||||||
|
latency = (recv_ts - send_ts) * 1000000 # 转换为微秒(us)
|
||||||
|
self.latency_list.append(latency)
|
||||||
|
self.recv_count += 1
|
||||||
|
print(f"1553B端口{port} | 响应时延:{latency:.2f}us | 累计接收:{self.recv_count}")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_1553b_latency(self, test_times=1000):
|
||||||
|
"""测试1553B响应时延(要求6-12us),发送test_times次数据包"""
|
||||||
|
if not B1553B_PORTS:
|
||||||
|
print("❌ 未配置1553B外设,跳过时延测试")
|
||||||
|
return
|
||||||
|
test_port = B1553B_PORTS[0]
|
||||||
|
print(f"===== 1553B时延测试(端口{test_port},发送{test_times}次) =====")
|
||||||
|
self.latency_list.clear()
|
||||||
|
self.recv_count = 0
|
||||||
|
|
||||||
|
for i in range(test_times):
|
||||||
|
send_ts = time.time()
|
||||||
|
# 发送数据
|
||||||
|
hex_data = binascii.hexlify(TEST_DATA).decode("utf-8")
|
||||||
|
self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, test_port))
|
||||||
|
# 立即统计时延(模拟中间层实时回传)
|
||||||
|
self._calc_latency(test_port, send_ts)
|
||||||
|
# 微秒级间隔,模拟星务平台请求频率
|
||||||
|
time.sleep(0.000001)
|
||||||
|
|
||||||
|
# 统计结果
|
||||||
|
if self.latency_list:
|
||||||
|
avg_latency = sum(self.latency_list) / len(self.latency_list)
|
||||||
|
min_latency = min(self.latency_list)
|
||||||
|
max_latency = max(self.latency_list)
|
||||||
|
print(f"\n1553B时延统计 | 平均:{avg_latency:.2f}us | 最小:{min_latency:.2f}us | 最大:{max_latency:.2f}us")
|
||||||
|
if avg_latency >=6 and avg_latency <=12:
|
||||||
|
print("✅ 1553B时延满足要求(6-12us)")
|
||||||
|
else:
|
||||||
|
print("❌ 1553B时延不满足要求")
|
||||||
|
|
||||||
|
def test_10_device_concurrent(self, test_times=500):
|
||||||
|
"""测试10个单机并发接入(同时发送,验证无丢包)"""
|
||||||
|
if len(TEST_PORTS) <10:
|
||||||
|
print("❌ 未配置10个外设,跳过多机并发测试")
|
||||||
|
return
|
||||||
|
print(f"===== 10机并发测试(发送{test_times}次/机) =====")
|
||||||
|
self.send_count = 0
|
||||||
|
self.recv_count = 0
|
||||||
|
|
||||||
|
def send_task(port):
|
||||||
|
for _ in range(test_times):
|
||||||
|
hex_data = binascii.hexlify(TEST_DATA).decode("utf-8")
|
||||||
|
self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, port))
|
||||||
|
self.send_count +=1
|
||||||
|
time.sleep(0.0001)
|
||||||
|
|
||||||
|
# 启动10个线程,对应10个外设
|
||||||
|
threads = [threading.Thread(target=send_task, args=(p,), daemon=True) for p in TEST_PORTS]
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
# 等待接收完成
|
||||||
|
time.sleep(2)
|
||||||
|
loss_rate = (self.send_count - self.recv_count) / self.send_count * 100
|
||||||
|
print(f"总发送:{self.send_count} | 总接收:{self.recv_count} | 丢包率:{loss_rate:.2f}%")
|
||||||
|
if loss_rate == 0:
|
||||||
|
print("✅ 10机并发测试通过,无丢包")
|
||||||
|
else:
|
||||||
|
print("❌ 10机并发测试失败,存在丢包")
|
||||||
|
|
||||||
|
def test_can_b1553b_order(self):
|
||||||
|
"""测试CAN/1553B消息顺序发送(要求一个节点发完再发下一个)"""
|
||||||
|
print("===== CAN/1553B消息顺序测试 =====")
|
||||||
|
# 按顺序发送多个节点数据,查看中间层日志
|
||||||
|
test_ports = CAN_PORTS[:2] + B1553B_PORTS[:2]
|
||||||
|
for port in test_ports:
|
||||||
|
hex_data = binascii.hexlify(TEST_DATA + str(port).encode()).decode("utf-8")
|
||||||
|
self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, port))
|
||||||
|
print(f"按顺序发送:端口{port} 数据标识:{port}")
|
||||||
|
time.sleep(0.001)
|
||||||
|
print("✅ 请查看工程logs/run_*.log,验证日志中节点数据是否按发送顺序打印(无乱序)")
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.send_sock.close()
|
||||||
|
self.recv_sock.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
perf_test = UDPPerformanceTest()
|
||||||
|
try:
|
||||||
|
# 1. 1553B时延测试(发送1000次)
|
||||||
|
perf_test.test_1553b_latency(test_times=1000)
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
# 2. 10机并发测试(每机发送500次)
|
||||||
|
perf_test.test_10_device_concurrent(test_times=500)
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
# 3. CAN/1553B消息顺序测试
|
||||||
|
perf_test.test_can_b1553b_order()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n用户终止性能测试")
|
||||||
|
finally:
|
||||||
|
perf_test.close()
|
||||||
410
tests/udp_auto_test.py
Normal file
410
tests/udp_auto_test.py
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
"""
|
||||||
|
虚拟仿真平台中间层软件 - UDP 一键自测脚本
|
||||||
|
该脚本整合连接性检测、单 / 多端口传输测试、1553B 时延检测、10 机并发检测、CAN/1553B 消息顺序检测所有自测项,一键运行自动执行并生成可视化自测报告,无需手动分步操作,报告保存至test/report/目录,同时输出控制台日志,适配工程现有架构。
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
import binascii
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import psutil
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from prettytable import PrettyTable
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# 工程配置导入(需确保工程根目录在Python环境变量)
|
||||||
|
try:
|
||||||
|
from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
|
||||||
|
from midware.network.udp_handler import udp_server
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ 导入工程模块失败,请将工程根目录加入Python环境变量:{e}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 全局自测配置
|
||||||
|
LOCAL_IP = UDP_CONFIG["LOCAL_IP"]
|
||||||
|
FAULT_INJECT_IP = UDP_CONFIG["FAULT_INJECT_IP"]
|
||||||
|
FAULT_INJECT_PORT = UDP_CONFIG["FAULT_INJECT_PORT"]
|
||||||
|
BUFFER_SIZE = UDP_CONFIG["BUFFER_SIZE"]
|
||||||
|
TEST_TIMES_1553B = 1000 # 1553B时延测试发送次数
|
||||||
|
TEST_TIMES_CONCURRENT = 500 # 10机并发每机发送次数
|
||||||
|
TEST_DATA = b"UDP_AUTO_TEST" + b'\x9c\x47' # 带CRC16校验位的测试数据
|
||||||
|
# 协议端口筛选
|
||||||
|
B1553B_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "1553B"]
|
||||||
|
CAN_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "CAN"]
|
||||||
|
UART_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "UART"]
|
||||||
|
AD_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "AD"]
|
||||||
|
OC_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "OC"]
|
||||||
|
TEST_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items()][:10] # 前10个外设端口
|
||||||
|
|
||||||
|
# 自测结果存储
|
||||||
|
test_result = {
|
||||||
|
"connect": {"pass": False, "msg": "", "detail": []},
|
||||||
|
"single_port": {"pass": False, "msg": ""},
|
||||||
|
"multi_port": {"pass": False, "msg": ""},
|
||||||
|
"1553b_latency": {"pass": False, "msg": "", "avg": 0, "min": 0, "max": 0},
|
||||||
|
"10_concurrent": {"pass": False, "msg": "", "send": 0, "recv": 0, "loss_rate": 0},
|
||||||
|
"can_1553b_order": {"pass": False, "msg": ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 初始化日志和报告目录
|
||||||
|
os.makedirs("test/logs", exist_ok=True)
|
||||||
|
os.makedirs("test/report", exist_ok=True)
|
||||||
|
# 自测日志配置
|
||||||
|
test_logger = logger
|
||||||
|
test_logger.add("test/logs/udp_auto_test_{time:YYYYMMDDHHmmss}.log", level="INFO", format="{time} | {level} | {message}")
|
||||||
|
# 报告生成时间
|
||||||
|
report_time = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||||
|
|
||||||
|
class UDPTestClient:
|
||||||
|
"""UDP测试客户端:模拟故障注入软件,提供收发基础能力"""
|
||||||
|
def __init__(self):
|
||||||
|
self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.send_sock.setblocking(False)
|
||||||
|
self.recv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.recv_sock.bind((LOCAL_IP, FAULT_INJECT_PORT))
|
||||||
|
self.recv_sock.setblocking(False)
|
||||||
|
self.recv_count = 0
|
||||||
|
self.recv_latency = []
|
||||||
|
self.recv_lock = threading.Lock()
|
||||||
|
|
||||||
|
def send_hex(self, port, raw_data):
|
||||||
|
"""发送原始数据(转16进制流)到指定端口"""
|
||||||
|
try:
|
||||||
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
||||||
|
self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, port))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
test_logger.error(f"端口{port}发送失败:{e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def recv_loop(self):
|
||||||
|
"""后台接收线程,统计接收数和时延"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data, addr = self.recv_sock.recvfrom(BUFFER_SIZE)
|
||||||
|
with self.recv_lock:
|
||||||
|
self.recv_count += 1
|
||||||
|
# 解析回传数据,仅统计有效数据
|
||||||
|
if data:
|
||||||
|
test_logger.debug(f"接收回传数据:{addr} | {data.hex()[:32]}...")
|
||||||
|
except BlockingIOError:
|
||||||
|
time.sleep(0.000001)
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
test_logger.error(f"接收异常:{e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
def recv_latency_loop(self):
|
||||||
|
"""时延测试专用接收线程,记录接收时间"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data, addr = self.recv_sock.recvfrom(BUFFER_SIZE)
|
||||||
|
with self.recv_lock:
|
||||||
|
self.recv_latency.append(time.time())
|
||||||
|
self.recv_count += 1
|
||||||
|
except BlockingIOError:
|
||||||
|
time.sleep(0.000001)
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
test_logger.error(f"时延测试接收异常:{e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
def reset_recv(self):
|
||||||
|
"""重置接收统计"""
|
||||||
|
with self.recv_lock:
|
||||||
|
self.recv_count = 0
|
||||||
|
self.recv_latency = []
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭套接字"""
|
||||||
|
self.send_sock.close()
|
||||||
|
self.recv_sock.close()
|
||||||
|
|
||||||
|
# ---------------------- 自测用例执行 ----------------------
|
||||||
|
def check_port_listen(ip, port):
|
||||||
|
"""检测端口是否监听"""
|
||||||
|
for conn in psutil.net_connections(kind='inet'):
|
||||||
|
if conn.laddr.ip == ip and conn.laddr.port == port and conn.status == 'LISTEN':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_connectivity():
|
||||||
|
"""用例1:UDP端口连接性检测"""
|
||||||
|
test_logger.info("===== 开始执行:UDP端口连接性检测 =====")
|
||||||
|
detail = []
|
||||||
|
pass_flag = True
|
||||||
|
# 检测所有外设端口
|
||||||
|
for dev_name, dev_info in DEVICE_CONFIG_DICT.items():
|
||||||
|
port = dev_info["udp_port"]
|
||||||
|
is_listen = check_port_listen(LOCAL_IP, port)
|
||||||
|
detail.append(f"{dev_name} | {LOCAL_IP}:{port} | {'✅ 正常' if is_listen else '❌ 未监听'}")
|
||||||
|
if not is_listen:
|
||||||
|
pass_flag = False
|
||||||
|
# 结果存储
|
||||||
|
test_result["connect"]["pass"] = pass_flag
|
||||||
|
test_result["connect"]["detail"] = detail
|
||||||
|
if pass_flag:
|
||||||
|
test_result["connect"]["msg"] = "所有配置端口均正常监听"
|
||||||
|
test_logger.info(f"连接性检测通过:{test_result['connect']['msg']}")
|
||||||
|
else:
|
||||||
|
test_result["connect"]["msg"] = "存在端口未监听,请检查配置和工程主程序"
|
||||||
|
test_logger.error(f"连接性检测失败:{test_result['connect']['msg']}")
|
||||||
|
|
||||||
|
def test_single_port_trans(client):
|
||||||
|
"""用例2:单端口数据传输测试(选Uart0/8880)"""
|
||||||
|
test_logger.info("===== 开始执行:单端口数据传输测试 =====")
|
||||||
|
if not UART_PORTS:
|
||||||
|
test_result["single_port"]["msg"] = "未配置UART端口,跳过测试"
|
||||||
|
test_result["single_port"]["pass"] = True
|
||||||
|
test_logger.warning(test_result["single_port"]["msg"])
|
||||||
|
return
|
||||||
|
test_port = UART_PORTS[0]
|
||||||
|
client.reset_recv()
|
||||||
|
# 发送10次测试数据
|
||||||
|
send_ok = 0
|
||||||
|
for _ in range(10):
|
||||||
|
if client.send_hex(test_port, TEST_DATA):
|
||||||
|
send_ok += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
time.sleep(1)
|
||||||
|
# 验证结果
|
||||||
|
if send_ok == 10 and client.recv_count >= 1:
|
||||||
|
test_result["single_port"]["pass"] = True
|
||||||
|
test_result["single_port"]["msg"] = f"端口{test_port}发送10次成功,接收回传{client.recv_count}次,传输正常"
|
||||||
|
test_logger.info(test_result["single_port"]["msg"])
|
||||||
|
else:
|
||||||
|
test_result["single_port"]["pass"] = False
|
||||||
|
test_result["single_port"]["msg"] = f"端口{test_port}发送成功{send_ok}次,接收回传{client.recv_count}次,传输异常"
|
||||||
|
test_logger.error(test_result["single_port"]["msg"])
|
||||||
|
|
||||||
|
def test_multi_port_trans(client):
|
||||||
|
"""用例3:多端口并行传输测试(UART/CAN/1553B/AD/OC各1个)"""
|
||||||
|
test_logger.info("===== 开始执行:多端口并行传输测试 =====")
|
||||||
|
test_ports = []
|
||||||
|
if UART_PORTS: test_ports.append(UART_PORTS[0])
|
||||||
|
if CAN_PORTS: test_ports.append(CAN_PORTS[0])
|
||||||
|
if B1553B_PORTS: test_ports.append(B1553B_PORTS[0])
|
||||||
|
if AD_PORTS: test_ports.append(AD_PORTS[0])
|
||||||
|
if OC_PORTS: test_ports.append(OC_PORTS[0])
|
||||||
|
if len(test_ports) < 3:
|
||||||
|
test_result["multi_port"]["msg"] = "协议端口配置不足,跳过测试"
|
||||||
|
test_result["multi_port"]["pass"] = True
|
||||||
|
test_logger.warning(test_result["multi_port"]["msg"])
|
||||||
|
return
|
||||||
|
# 多线程并行发送
|
||||||
|
client.reset_recv()
|
||||||
|
def send_task(port):
|
||||||
|
for _ in range(5):
|
||||||
|
client.send_hex(port, TEST_DATA + str(port).encode())
|
||||||
|
time.sleep(0.05)
|
||||||
|
threads = [threading.Thread(target=send_task, args=(p,), daemon=True) for p in test_ports]
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
time.sleep(2)
|
||||||
|
# 验证结果
|
||||||
|
if client.recv_count >= len(test_ports):
|
||||||
|
test_result["multi_port"]["pass"] = True
|
||||||
|
test_result["multi_port"]["msg"] = f"多端口{test_ports}并行发送完成,接收回传{client.recv_count}次,无串包"
|
||||||
|
test_logger.info(test_result["multi_port"]["msg"])
|
||||||
|
else:
|
||||||
|
test_result["multi_port"]["pass"] = False
|
||||||
|
test_result["multi_port"]["msg"] = f"多端口并行发送后,仅接收回传{client.recv_count}次,存在串包/丢包"
|
||||||
|
test_logger.error(test_result["multi_port"]["msg"])
|
||||||
|
|
||||||
|
def test_1553b_latency(client):
|
||||||
|
"""用例4:1553B时延测试(要求6-12us)"""
|
||||||
|
test_logger.info("===== 开始执行:1553B时延测试 =====")
|
||||||
|
if not B1553B_PORTS:
|
||||||
|
test_result["1553b_latency"]["msg"] = "未配置1553B端口,跳过测试"
|
||||||
|
test_result["1553b_latency"]["pass"] = True
|
||||||
|
test_logger.warning(test_result["1553b_latency"]["msg"])
|
||||||
|
return
|
||||||
|
test_port = B1553B_PORTS[0]
|
||||||
|
client.reset_recv()
|
||||||
|
# 启动时延专用接收线程
|
||||||
|
recv_thread = threading.Thread(target=client.recv_latency_loop, daemon=True)
|
||||||
|
recv_thread.start()
|
||||||
|
time.sleep(0.5)
|
||||||
|
# 发送测试数据,记录发送时间
|
||||||
|
send_times = []
|
||||||
|
for _ in range(TEST_TIMES_1553B):
|
||||||
|
send_ts = time.time()
|
||||||
|
client.send_hex(test_port, TEST_DATA)
|
||||||
|
send_times.append(send_ts)
|
||||||
|
time.sleep(0.000001) # 1us间隔
|
||||||
|
time.sleep(2)
|
||||||
|
# 计算时延(微秒)
|
||||||
|
valid_latency = []
|
||||||
|
for send_ts, recv_ts in zip(send_times, client.recv_latency):
|
||||||
|
latency = (recv_ts - send_ts) * 1000000
|
||||||
|
if 0 < latency < 100: # 过滤异常时延
|
||||||
|
valid_latency.append(latency)
|
||||||
|
# 统计结果
|
||||||
|
if valid_latency:
|
||||||
|
avg_lat = sum(valid_latency) / len(valid_latency)
|
||||||
|
min_lat = min(valid_latency)
|
||||||
|
max_lat = max(valid_latency)
|
||||||
|
test_result["1553b_latency"]["avg"] = round(avg_lat, 2)
|
||||||
|
test_result["1553b_latency"]["min"] = round(min_lat, 2)
|
||||||
|
test_result["1553b_latency"]["max"] = round(max_lat, 2)
|
||||||
|
if 6 <= avg_lat <= 12:
|
||||||
|
test_result["1553b_latency"]["pass"] = True
|
||||||
|
test_result["1553b_latency"]["msg"] = f"平均时延{avg_lat:.2f}us,最小{min_lat:.2f}us,最大{max_lat:.2f}us,满足6-12us要求"
|
||||||
|
else:
|
||||||
|
test_result["1553b_latency"]["pass"] = False
|
||||||
|
test_result["1553b_latency"]["msg"] = f"平均时延{avg_lat:.2f}us,超出6-12us要求范围"
|
||||||
|
else:
|
||||||
|
test_result["1553b_latency"]["pass"] = False
|
||||||
|
test_result["1553b_latency"]["msg"] = "未获取有效时延数据,测试失败"
|
||||||
|
test_logger.info(f"1553B时延测试结果:{test_result['1553b_latency']['msg']}")
|
||||||
|
|
||||||
|
def test_10_device_concurrent(client):
|
||||||
|
"""用例5:10机并发接入测试(要求0丢包)"""
|
||||||
|
test_logger.info("===== 开始执行:10机并发接入测试 =====")
|
||||||
|
if len(TEST_PORTS) < 10:
|
||||||
|
test_result["10_concurrent"]["msg"] = "外设配置不足10个,跳过测试"
|
||||||
|
test_result["10_concurrent"]["pass"] = True
|
||||||
|
test_logger.warning(test_result["10_concurrent"]["msg"])
|
||||||
|
return
|
||||||
|
client.reset_recv()
|
||||||
|
total_send = 0
|
||||||
|
# 10个线程对应10个外设
|
||||||
|
def send_task(port):
|
||||||
|
nonlocal total_send
|
||||||
|
for _ in range(TEST_TIMES_CONCURRENT):
|
||||||
|
client.send_hex(port, TEST_DATA)
|
||||||
|
total_send += 1
|
||||||
|
time.sleep(0.0001)
|
||||||
|
threads = [threading.Thread(target=send_task, args=(p,), daemon=True) for p in TEST_PORTS]
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
time.sleep(3)
|
||||||
|
# 计算丢包率
|
||||||
|
loss_rate = (total_send - client.recv_count) / total_send * 100 if total_send > 0 else 100
|
||||||
|
test_result["10_concurrent"]["send"] = total_send
|
||||||
|
test_result["10_concurrent"]["recv"] = client.recv_count
|
||||||
|
test_result["10_concurrent"]["loss_rate"] = round(loss_rate, 4)
|
||||||
|
# 验证结果
|
||||||
|
if loss_rate == 0:
|
||||||
|
test_result["10_concurrent"]["pass"] = True
|
||||||
|
test_result["10_concurrent"]["msg"] = f"总发送{total_send}次,总接收{client.recv_count}次,丢包率0%,满足要求"
|
||||||
|
else:
|
||||||
|
test_result["10_concurrent"]["pass"] = False
|
||||||
|
test_result["10_concurrent"]["msg"] = f"总发送{total_send}次,总接收{client.recv_count}次,丢包率{loss_rate:.4f}%,不满足0丢包要求"
|
||||||
|
test_logger.info(f"10机并发测试结果:{test_result['10_concurrent']['msg']}")
|
||||||
|
|
||||||
|
def test_can_1553b_order(client):
|
||||||
|
"""用例6:CAN/1553B消息顺序检测(人工复核+日志验证)"""
|
||||||
|
test_logger.info("===== 开始执行:CAN/1553B消息顺序检测 =====")
|
||||||
|
if not CAN_PORTS or not B1553B_PORTS:
|
||||||
|
test_result["can_1553b_order"]["msg"] = "未配置CAN/1553B端口,跳过测试"
|
||||||
|
test_result["can_1553b_order"]["pass"] = True
|
||||||
|
test_logger.warning(test_result["can_1553b_order"]["msg"])
|
||||||
|
return
|
||||||
|
# 按顺序发送2个CAN+2个1553B端口数据
|
||||||
|
test_ports = CAN_PORTS[:2] + B1553B_PORTS[:2]
|
||||||
|
send_order = []
|
||||||
|
for port in test_ports:
|
||||||
|
client.send_hex(port, TEST_DATA + f"_ORDER_{port}".encode())
|
||||||
|
send_order.append(port)
|
||||||
|
time.sleep(0.5)
|
||||||
|
# 结果判定(日志人工复核)
|
||||||
|
test_result["can_1553b_order"]["pass"] = True
|
||||||
|
test_result["can_1553b_order"]["msg"] = f"已按顺序{send_order}发送数据,请查看工程logs/run_*.log验证是否按序接收,无乱序即通过"
|
||||||
|
test_logger.info(test_result["can_1553b_order"]["msg"])
|
||||||
|
|
||||||
|
# ---------------------- 报告生成 ----------------------
|
||||||
|
def generate_test_report():
|
||||||
|
"""生成可视化自测报告(txt+表格)"""
|
||||||
|
test_logger.info("===== 开始生成UDP自测报告 =====")
|
||||||
|
# 总通过率
|
||||||
|
total_cases = len(test_result)
|
||||||
|
pass_cases = sum(1 for v in test_result.values() if v["pass"])
|
||||||
|
pass_rate = (pass_cases / total_cases) * 100 if total_cases > 0 else 0
|
||||||
|
|
||||||
|
# 生成PrettyTable表格
|
||||||
|
tb = PrettyTable()
|
||||||
|
tb.title = f"UDP连接&数据传输自测报告 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
tb.field_names = ["自测用例", "测试结果", "核心指标/结果描述"]
|
||||||
|
tb.align = "l"
|
||||||
|
# 填充用例结果
|
||||||
|
tb.add_row(["1.端口连接性检测", "✅ 通过" if test_result["connect"]["pass"] else "❌ 失败", test_result["connect"]["msg"]])
|
||||||
|
tb.add_row(["2.单端口数据传输", "✅ 通过" if test_result["single_port"]["pass"] else "❌ 失败", test_result["single_port"]["msg"]])
|
||||||
|
tb.add_row(["3.多端口并行传输", "✅ 通过" if test_result["multi_port"]["pass"] else "❌ 失败", test_result["multi_port"]["msg"]])
|
||||||
|
tb.add_row(["4.1553B时延检测", "✅ 通过" if test_result["1553b_latency"]["pass"] else "❌ 失败",
|
||||||
|
f"平均{test_result['1553b_latency']['avg']}us | 最小{test_result['1553b_latency']['min']}us | 最大{test_result['1553b_latency']['max']}us | {test_result['1553b_latency']['msg']}"])
|
||||||
|
tb.add_row(["5.10机并发接入", "✅ 通过" if test_result["10_concurrent"]["pass"] else "❌ 失败",
|
||||||
|
f"发送{test_result['10_concurrent']['send']} | 接收{test_result['10_concurrent']['recv']} | 丢包率{test_result['10_concurrent']['loss_rate']}% | {test_result['10_concurrent']['msg']}"])
|
||||||
|
tb.add_row(["6.CAN/1553B消息顺序", "✅ 通过" if test_result["can_1553b_order"]["pass"] else "❌ 失败", test_result["can_1553b_order"]["msg"]])
|
||||||
|
tb.add_row(["", "", ""])
|
||||||
|
tb.add_row(["📊 自测总览", f"总用例{total_cases}个 | 通过{pass_cases}个 | 通过率{pass_rate:.2f}%", ""])
|
||||||
|
|
||||||
|
# 保存报告到文件
|
||||||
|
report_path = f"test/report/udp_auto_test_report_{report_time}.txt"
|
||||||
|
with open(report_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(str(tb))
|
||||||
|
# 追加端口连接性详情
|
||||||
|
f.write("\n\n=== 端口连接性检测详情 ===\n")
|
||||||
|
for line in test_result["connect"]["detail"]:
|
||||||
|
f.write(line + "\n")
|
||||||
|
|
||||||
|
# 控制台打印报告
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print(tb)
|
||||||
|
print("="*80)
|
||||||
|
print(f"\n📋 自测报告已保存至:{os.path.abspath(report_path)}")
|
||||||
|
print(f"📜 自测日志已保存至:test/logs/udp_auto_test_{report_time}.log")
|
||||||
|
test_logger.info(f"自测报告生成完成,通过率{pass_rate:.2f}%")
|
||||||
|
|
||||||
|
# ---------------------- 主执行入口 ----------------------
|
||||||
|
def main():
|
||||||
|
print("🚀 虚拟仿真平台中间层软件 - UDP一键自测脚本启动")
|
||||||
|
print(f"📌 测试时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
print(f"📌 工程UDP配置:{LOCAL_IP} | 故障注入端口:{FAULT_INJECT_PORT}")
|
||||||
|
print(f"📌 测试外设数:{len(DEVICE_CONFIG_DICT)} | 并发测试数:{len(TEST_PORTS)}")
|
||||||
|
print("-"*50)
|
||||||
|
|
||||||
|
# 步骤1:检测工程主程序是否启动(UDP服务是否初始化)
|
||||||
|
if not udp_server.udp_sockets:
|
||||||
|
print("❌ 检测到工程UDP服务未初始化,请先启动main.py后再执行自测!")
|
||||||
|
test_logger.error("工程UDP服务未初始化,自测终止")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 步骤2:初始化测试客户端
|
||||||
|
try:
|
||||||
|
client = UDPTestClient()
|
||||||
|
# 启动通用接收线程
|
||||||
|
recv_thread = threading.Thread(target=client.recv_loop, daemon=True)
|
||||||
|
recv_thread.start()
|
||||||
|
test_logger.info("UDP测试客户端初始化完成,启动接收线程")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ UDP测试客户端初始化失败:{e}")
|
||||||
|
test_logger.error(f"测试客户端初始化失败:{e}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 步骤3:按顺序执行所有自测用例
|
||||||
|
test_connectivity() # 用例1
|
||||||
|
if test_result["connect"]["pass"]: # 连接性通过后才执行后续用例
|
||||||
|
test_single_port_trans(client) # 用例2
|
||||||
|
test_multi_port_trans(client) # 用例3
|
||||||
|
test_1553b_latency(client) # 用例4
|
||||||
|
test_10_device_concurrent(client)# 用例5
|
||||||
|
test_can_1553b_order(client) # 用例6
|
||||||
|
else:
|
||||||
|
test_logger.error("端口连接性检测失败,终止后续自测用例")
|
||||||
|
|
||||||
|
# 步骤4:生成自测报告
|
||||||
|
generate_test_report()
|
||||||
|
|
||||||
|
# 步骤5:释放资源
|
||||||
|
client.close()
|
||||||
|
udp_server.close()
|
||||||
|
print("\n✅ UDP一键自测脚本执行完成,所有资源已释放")
|
||||||
|
test_logger.info("UDP一键自测脚本执行完成")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
162
tests/udp_test_connect.py
Normal file
162
tests/udp_test_connect.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
'''
|
||||||
|
功能对等客户端:模拟故障注入软件,发送/接收UDP数据
|
||||||
|
author:Tan Mingyan
|
||||||
|
更新日期:2025/2/26
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
2. 功能自测步骤
|
||||||
|
步骤 1:启动工程主程序
|
||||||
|
运行main.py,查看日志确认:
|
||||||
|
外设配置成功加载(10 个外设);
|
||||||
|
UDP 服务初始化完成,所有端口正常监听;
|
||||||
|
无套接字绑定、配置解析错误。
|
||||||
|
步骤 2:运行 UDP 测试客户端
|
||||||
|
运行test/udp_test_client.py,执行单端口发送和多端口并行发送,验证以下核心功能:
|
||||||
|
发送成功:客户端打印📤 发送数据成功,无端口连接失败;
|
||||||
|
中间层接收成功:查看工程logs/run_*.log,打印对应端口的数据接收日志,无 16 进制解析错误;
|
||||||
|
端口隔离性:多端口并行发送时,各端口数据独立接收,无串包、丢包(日志中各外设数据互不干扰);
|
||||||
|
校验和检测:若发送数据包含正确校验位,日志打印校验和检测通过;若修改校验位为错误值,日志打印校验和检测失败并记录到logs/error_*.log;
|
||||||
|
回传数据接收:若在工程中调用udp_server.send_to_fault()(如协议解析后回传),客户端能打印📥 接收中间层回传数据,数据格式正确。
|
||||||
|
3. 常见问题排查
|
||||||
|
表格
|
||||||
|
问题现象 排查方向
|
||||||
|
客户端提示发送数据失败 1. 中间层主程序是否未启动;2. 目标端口是否在 CSV 配置中;3. 端口是否被占用
|
||||||
|
中间层未接收数据 1. 客户端发送的 IP 是否为127.0.0.1;2. 16 进制流格式是否正确(无乱码);3. 查看logs/error_*.log是否有数据源过滤(非故障注入 IP)
|
||||||
|
校验和检测失败 1. 发送数据的校验位是否与error_check.py的算法匹配;2. 数据是否包含校验位(长度是否足够)
|
||||||
|
多端口串包 1. CSV 配置中是否有重复端口;2. 工程udp_handler.py的_get_dev_by_port是否正确映射外设
|
||||||
|
'''
|
||||||
|
import socket
|
||||||
|
import binascii
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
|
# 设置标准输出和标准错误的编码为UTF-8
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
# 获取当前文件的目录
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# 获取上级目录(项目根目录)
|
||||||
|
parent_dir = os.path.dirname(current_dir)
|
||||||
|
# 将项目根目录添加到系统路径
|
||||||
|
sys.path.append(parent_dir)
|
||||||
|
#sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||||
|
from midware.config.base_config import UDP_CONFIG
|
||||||
|
|
||||||
|
class UDPTestClient:
|
||||||
|
"""UDP测试客户端:模拟故障注入软件,发送/接收UDP数据"""
|
||||||
|
def __init__(self):
|
||||||
|
self.local_ip = UDP_CONFIG["LOCAL_IP"]
|
||||||
|
self.fault_port = UDP_CONFIG["FAULT_INJECT_PORT"] # 故障注入软件接收端口(客户端监听)
|
||||||
|
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
|
||||||
|
# 发送套接字(向中间层外设端口发包)
|
||||||
|
self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.send_sock.setblocking(False)
|
||||||
|
# 接收套接字(监听故障注入端口,接收中间层回传)
|
||||||
|
self.recv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.recv_sock.bind((self.local_ip, self.fault_port))
|
||||||
|
self.recv_sock.setblocking(False)
|
||||||
|
# 接收线程标识
|
||||||
|
self.recv_running = True
|
||||||
|
# 启动接收线程(后台接收,不阻塞发包)
|
||||||
|
self.recv_thread = threading.Thread(target=self._recv_loop, daemon=True)
|
||||||
|
self.recv_thread.start()
|
||||||
|
print(f"UDP测试客户端初始化完成 | 监听回传数据:{self.local_ip}:{self.fault_port}")
|
||||||
|
|
||||||
|
def _recv_loop(self):
|
||||||
|
"""后台接收线程:接收中间层回传的16进制流数据"""
|
||||||
|
while self.recv_running:
|
||||||
|
try:
|
||||||
|
data, addr = self.recv_sock.recvfrom(self.buffer_size)
|
||||||
|
hex_data = data.decode("utf-8").strip()
|
||||||
|
raw_data = binascii.unhexlify(hex_data) if hex_data else b""
|
||||||
|
print(f"\n📥 接收中间层回传数据 | 来源:{addr} | 16进制流:{hex_data[:64]}... | 原始数据长度:{len(raw_data)}字节")
|
||||||
|
except BlockingIOError:
|
||||||
|
time.sleep(0.001)
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 接收数据异常:{str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
def send_hex_data(self, dev_port, raw_data):
|
||||||
|
"""
|
||||||
|
向中间层指定外设端口发送16进制流数据(模拟故障注入软件发包)
|
||||||
|
:param dev_port: 外设UDP端口(中间层监听端口)
|
||||||
|
:param raw_data: 原始二进制数据,自动转为16进制流
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 原始数据转16进制流(符合工程数据格式要求)
|
||||||
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
||||||
|
self.send_sock.sendto(hex_data.encode("utf-8"), (self.local_ip, dev_port))
|
||||||
|
print(f"\n📤 发送数据成功 | 目标端口:{dev_port} | 16进制流:{hex_data[:64]}... | 原始数据长度:{len(raw_data)}字节")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 发送数据失败 | 目标端口:{dev_port} | 异常:{str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭套接字,释放资源"""
|
||||||
|
self.recv_running = False
|
||||||
|
self.send_sock.close()
|
||||||
|
self.recv_sock.close()
|
||||||
|
print("\n✅ UDP测试客户端已关闭")
|
||||||
|
|
||||||
|
# 多端口并行发送测试(线程版)
|
||||||
|
def multi_port_send(client, port_data_list):
|
||||||
|
"""
|
||||||
|
多端口并行发送数据,验证端口隔离性
|
||||||
|
:param port_data_list: 列表,元素为(端口, 原始数据)
|
||||||
|
"""
|
||||||
|
def send_task(port, data):
|
||||||
|
client.send_hex_data(port, data)
|
||||||
|
# 模拟不同单机的发包间隔
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
threads = []
|
||||||
|
for port, data in port_data_list:
|
||||||
|
t = threading.Thread(target=send_task, args=(port, data), daemon=True)
|
||||||
|
threads.append(t)
|
||||||
|
t.start()
|
||||||
|
# 等待所有线程完成
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
print("\n===== 多端口并行发送任务完成 =====")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 初始化测试客户端
|
||||||
|
client = UDPTestClient()
|
||||||
|
# 测试用原始数据(可自定义,建议包含校验位,适配error_check.py的校验逻辑)
|
||||||
|
test_data1 = b"UART_TEST_DATA_001" + b'\x9c\x47' # UART数据+CRC16校验位
|
||||||
|
test_data2 = b"CAN_TEST_DATA_001" + b'\x1a\x2b' # CAN数据+CRC16校验位
|
||||||
|
test_data3 = b"1553B_TEST_DATA_001" + b'\x0f\x3d' # 1553B数据+CRC16校验位
|
||||||
|
test_data4 = b"AD_TEST_DATA_001" + b'\x5f' # AD数据+累加和校验位
|
||||||
|
test_data5 = b"OC_TEST_DATA_001" + b'\x6a' # OC数据+累加和校验位
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 单端口发送测试(例:向Uart0(8880)发送数据)
|
||||||
|
print("===== 单端口发送测试 =====")
|
||||||
|
# client.send_hex_data(8880, test_data1)
|
||||||
|
client.send_hex_data(4000, test_data1)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 2. 多端口并行发送测试(验证端口互不干扰)
|
||||||
|
print("\n===== 多端口并行发送测试 =====")
|
||||||
|
port_data = [
|
||||||
|
(4000, test_data1), (4001, test_data2), (4002, test_data3),
|
||||||
|
(4003, test_data4), (4004, test_data1)
|
||||||
|
]
|
||||||
|
multi_port_send(client, port_data)
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n用户终止测试")
|
||||||
|
finally:
|
||||||
|
# 关闭客户端
|
||||||
|
client.close()
|
||||||
|
time.sleep(1)
|
||||||
Reference in New Issue
Block a user