import time import pytest import logging from typing import Tuple, Generator, List, Optional, Union from device.DeviceSerial import DeviceSerial from device.LaminaAdapter import LaminaAdapter from device.tools.ByteConv import display_hex TestCase_Controller = { # 测试用例定义 # 0 - 正常写入数据序列; [(写入数据, 读取数据)] # 1 - 范围限制测试; (最小值, 最大值) # 2 - 相关大小限制测试; ((0-等于, 1-小于, 2-大于), 相关值地址, 死区范围) 0x60: {0: [(0, '0x0000'), (1, '0x0001'), (2, '0x0001'), (0xFF, '0x0001'), (0xFFFF, '0x0001')]}, # 整机运行使能 0x61: {0: [(60.0, 60.0)], 1: (40, 100), 2: (1, 0x62, 0.5)}, # 最小启动允许输入电压 0x62: {0: [(440.0, 440.0)], 1: (350, 560), 2: (2, 0x61, 0.5)}, # 最大启动允许输入电压 0x63: {0: [(58, 58.0)], 1: (38, 98), 2: (1, 0x64, 0.5)}, # 最小停机输入电压 0x64: {0: [(442, 442.0)], 1: (352, 562), 2: (2, 0x63, 0.5)}, # 最大停机输入电压 0x65: {0: [(41, 41.0)], 1: (30, 60), 2: (1, 0x66, 0.5)}, # 最小启动允许输出电压 0x66: {0: [(57, 57.0)], 1: (50, 80), 2: (2, 0x65, 0.5)}, # 最大启动允许输出电压 0x67: {0: [(40, 40.0)], 1: (28, 58), 2: (1, 0x68, 0.5)}, # 最小停止允许输出电压 0x68: {0: [(58, 58.0)], 1: (52, 82), 2: (2, 0x67, 0.5)}, # 最大停止允许输出电压 0x69: {0: [(0.1, 0.1)], 1: (0, 5), 2: (1, 0x6A, 1.0)}, # 最小MPPT电流限值 0x6A: {0: [(22, 22.0)], 1: (5, 30), 2: (2, 0x69, 1.0)}, # 最大MPPT电流限值 0x6C: {0: [(6000, 6000)], 1: (3000, 7999.999),2: (0, 0x6E, 0.0)}, # 最大功率限值(由于转换误差, 无法写入8000W) 0x6E: {0: [(6000, 6000)], 1: (3000, 7999.999), }, # 最大功率限值存储值(由于转换误差, 无法写入8000W) 0x70: {0: [(500, 500)], 1: (400, 800), }, # Boost输入过压保护值 0x71: {0: [(460, 460)], 1: (300, 600), }, # Boost输出过压保护值 0x72: {0: [(60, 60)], 1: (40, 80), 2: (2, 0x73, 0.5)}, # LLC输出过压保护值 0x73: {0: [(40, 40)], 1: (20, 50), 2: (1, 0x72, 0.5)}, # LLC输出欠压保护值 0x74: {0: [(20, 20)], 1: (6, 30), }, # Boost电感过流保护值 0x75: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流均值保护值 0x76: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流峰值保护值 0x78: {0: [(6200, 6200)], 1: (4500, 9000), }, # 过载保护值 0x7A: {0: [(105, 105)], 1: (30, 150), 2: (2, 0x7B, 1.0)}, # 过温故障值 0x7B: {0: [(95, 95)], 1: (20, 150), 2: (1, 0x7A, 1.0)}, # 过温告警值 0x7C: {0: [(85, 85)], 1: (0, 120), 2: (1, 0x7A, 1.0)}, # 过温恢复值 0x7D: {0: [(10, 10)], 1: (0, 60), }, # 输出继电器故障判断差值 0x7E: {0: [(55, 55)], 1: (35, 60), }, # LLC输出电压给定值 0x7F: {0: [(420, 420)], 1: (320, 460), }, # Boost输出电压给定值 0x80: {0: [(56, 56)], 1: (10, 80), }, # 三点法中间阈值 0x81: {0: [(57, 57)], 1: (10, 80), }, # 浮充电压 0x82: {0: [(56, 56)], 1: (10, 80), }, # 恒压充电电压 0x83: {0: [(380, 380)], 1: (0, 450), }, # llc软起开始电压 0x84: {0: [(395, 395)], 1: (0, 450), 2: (1, 0x85, 0.5)}, # boost开始运行电压 0x85: {0: [(410, 410)], 1: (0, 450), 2: (2, 0x84, 0.5)}, # boost停止运行电压 0x86: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测正阻抗限值 0x88: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测负阻抗限值 0x8A: {0: [(123, 123), (137, 137)], 1: (50, 200), 2: (1, 0x8B, 0.2)}, # 抖动频率下限(精度误差严重, 难以正常测试) 0x8B: {0: [(137, 137), (123, 123)], 1: (50, 200), 2: (2, 0x8A, -0.2)}, # 抖动频率上限(精度误差严重, 难以正常测试) 0x170: {0: [(b'TTE0102DX20241001120001', 'TTE0102DX20241001120001\x00\x00\x00\x00\x00\x00\x00\x00\x00')]}, # 设备序列号 0x180: {0: [([0x24, 0x10, 0x01, 0x12, 0x00, 0x01], '$\x10\x01\x12\x00\x01' + 26 * '\x00')]}, # 设备MES码 0x190: {0: [(b'241001120001', '241001120001' + 20 * '\000')]}, # 出厂日期批次 } TestCase_Adapter = { # 测试用例定义 # 0 - 正常写入数据序列; [(写入数据, 读取数据)] # 1 - 范围限制测试; (最小值, 最大值) # 2 - 相关大小限制测试; ((0-等于, 1-小于, 2-大于), 相关值地址, 死区范围) 0x60: {0: [(0, '0x0000'), (1, '0x0001'), (2, '0x0001'), (0xFF, '0x0001'), (0xFFFF, '0x0001')]}, # 整机运行使能 0x61: {0: [(60.0, 60.0)], 1: (40, 100), 2: (1, 0x62, 0.5)}, # 最小启动允许输入电压 0x62: {0: [(440.0, 440.0)], 1: (350, 560), 2: (2, 0x61, 0.5)}, # 最大启动允许输入电压 0x63: {0: [(58, 58.0)], 1: (38, 98), 2: (1, 0x64, 0.5)}, # 最小停机输入电压 0x64: {0: [(442, 442.0)], 1: (352, 562), 2: (2, 0x63, 0.5)}, # 最大停机输入电压 0x65: {0: [(41, 41.0)], 1: (30, 60), 2: (1, 0x66, 0.5)}, # 最小启动允许输出电压 0x66: {0: [(57, 57.0)], 1: (50, 80), 2: (2, 0x65, 0.5)}, # 最大启动允许输出电压 0x67: {0: [(40, 40.0)], 1: (28, 58), 2: (1, 0x68, 0.5)}, # 最小停止允许输出电压 0x68: {0: [(58, 58.0)], 1: (52, 82), 2: (2, 0x67, 0.5)}, # 最大停止允许输出电压 0x69: {0: [(0.1, 0.1)], 1: (0, 5), 2: (1, 0x6A, 1.0)}, # 最小MPPT电流限值 0x6A: {0: [(22, 22.0)], 1: (5, 30), 2: (2, 0x69, 1.0)}, # 最大MPPT电流限值 0x6C: {0: [(6000, 6000)], 1: (3000, 7999.999),2: (0, 0x6E, 0.0)}, # 最大功率限值(由于转换误差, 无法写入8000W) 0x6E: {0: [(6000, 6000)], 1: (3000, 7999.999), }, # 最大功率限值存储值(由于转换误差, 无法写入8000W) 0x70: {0: [(500, 500)], 1: (400, 800), }, # Boost输入过压保护值 0x71: {0: [(460, 460)], 1: (300, 600), }, # Boost输出过压保护值 0x72: {0: [(60, 60)], 1: (40, 80), 2: (2, 0x73, 0.5)}, # LLC输出过压保护值 0x73: {0: [(40, 40)], 1: (20, 50), 2: (1, 0x72, 0.5)}, # LLC输出欠压保护值 0x74: {0: [(20, 20)], 1: (6, 30), }, # Boost电感过流保护值 0x75: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流均值保护值 0x76: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流峰值保护值 0x78: {0: [(6200, 6200)], 1: (4500, 9000), }, # 过载保护值 0x7A: {0: [(105, 105)], 1: (30, 150), 2: (2, 0x7B, 1.0)}, # 过温故障值 0x7B: {0: [(95, 95)], 1: (20, 150), 2: (1, 0x7A, 1.0)}, # 过温告警值 0x7C: {0: [(85, 85)], 1: (0, 120), 2: (1, 0x7A, 1.0)}, # 过温恢复值 0x7D: {0: [(10, 10)], 1: (0, 60), }, # 输出继电器故障判断差值 0x7E: {0: [(55, 55)], 1: (35, 60), }, # LLC输出电压给定值 0x7F: {0: [(420, 420)], 1: (320, 460), }, # Boost输出电压给定值 0x80: {0: [(56, 56)], 1: (10, 80), }, # 三点法中间阈值 0x81: {0: [(57, 57)], 1: (10, 80), }, # 浮充电压 0x82: {0: [(56, 56)], 1: (10, 80), }, # 恒压充电电压 0x83: {0: [(380, 380)], 1: (0, 450), }, # llc软起开始电压 0x84: {0: [(395, 395)], 1: (0, 450), 2: (1, 0x85, 0.5)}, # boost开始运行电压 0x85: {0: [(410, 410)], 1: (0, 450), 2: (2, 0x84, 0.5)}, # boost停止运行电压 0x86: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测正阻抗限值 0x88: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测负阻抗限值 0x8A: {0: [(123, 123), (137, 137)], 1: (50, 200), 2: (1, 0x8B, 0.2)}, # 抖动频率下限(精度误差严重, 难以正常测试) 0x8B: {0: [(137, 137), (123, 123)], 1: (50, 200), 2: (2, 0x8A, -0.2)}, # 抖动频率上限(精度误差严重, 难以正常测试) 0x170: {0: [(b'TTE0102DX20241001120001', 'TTE0102DX20241001120001\x00\x00\x00\x00\x00\x00\x00\x00\x00')]}, # 设备序列号 0x180: {0: [([0x24, 0x10, 0x01, 0x12, 0x00, 0x01], '$\x10\x01\x12\x00\x01' + 26 * '\x00')]}, # 设备MES码 0x190: {0: [(b'241001120001', '241001120001' + 20 * '\000')]}, # 出厂日期批次 } def frame_write(device:DeviceSerial, address, length, value): """ 整合参数写入接口 """ return device.frame_write(address, length, value) def frame_read(device:DeviceSerial, address, length=None): """ 整合参数读取接口 """ if length is None: info = device.block['data']['data_define'][address] length = 2 if info[1] in [3, 6] else 1 length = info[2] if info[1] in [4, 5, 7] else length return device.output['Regs'][address][1] if device.frame_read(address, length) else None def value_case_generator(device:DeviceSerial, address:int, itemCase:dict) -> Generator[object, object, bool]: """ 依据地址与参数配置生成测试用例 """ addr_relate = None list_case_normal = [] match itemCase.keys(): case 0: # 常规读写用例测试 for data_write, data_read in itemCase[0]: logging.info(f"Param Case:\taddr={display_hex(address)}, \tvalue={data_write}") yield data_write, data_read, True case 2: # 存在大小约束相关项 assert device.block['data']['data_define'][address][1] in [2, 3] type_relate, addr_relate, dead_zone = itemCase[2] val_relate = frame_read(device, addr_relate) yield val_relate, val_relate, False pass case 1: # 写入范围用例测试 val_min, val_max = itemCase[1] pass if 1 in itemCase.keys(): """ 写入范围用例测试 """ testzone = 0.7 if info_param[1] in [2, 3] and len(info_param) < 3 else 7 / info_param[2] accuracy = 0.20001 if info_param[1] in [2, 3] and len(info_param) < 3 else 2.0001 / info_param[2] val_min, val_max = itemCase[1] if 2 in itemCase.keys(): """ 存在大小约束相关项 """ list_case_relate = [] # 对约束相关项的修改 list_case_late = [] # 存在约束相关项影响后的测试用例 mode_relate, addr_relate, deadzone = itemCase[2] val_relate = frame_read(device, addr_relate, ParamCase[addr_relate]) if mode_relate == 1: if (val_relate - deadzone) < val_max: """ 约束项已限制写入范围 """ list_case_relate.append((val_max + 2 * abs(deadzone), val_max + 2 * abs(deadzone), True)) list_case_late.append((val_max + testzone, val_max + testzone, False)) list_case_late.append((val_max, val_max, True)) list_case_late.append((val_max - testzone, val_max - testzone, True)) val_max = val_relate - deadzone else: """ 约束项未限制写入范围 """ val_relate = val_max list_case_relate.append((val_relate, val_relate, True)) list_case_late.append((val_relate - deadzone + testzone, val_relate - deadzone + testzone, False)) list_case_late.append((val_relate - deadzone, val_relate - deadzone, True)) list_case_late.append((val_relate - deadzone - testzone, val_relate - deadzone - testzone, True)) elif mode_relate == 2: if (val_relate + deadzone) > val_min: """ 约束项已限制写入范围 """ list_case_relate.append((val_min - 2 * abs(deadzone), val_min - 2 * abs(deadzone), True)) list_case_late.append((val_min - testzone, val_min - testzone, False)) list_case_late.append((val_min, val_min, True)) list_case_late.append((val_min + testzone, val_min + testzone, True)) val_min = val_relate + deadzone else: """ 约束项未限制写入范围 """ val_relate = val_min list_case_relate.append((val_relate, val_relate, True)) list_case_late.append((val_relate + deadzone - testzone, val_relate + deadzone - testzone, False)) list_case_late.append((val_relate + deadzone, val_relate + deadzone, True)) list_case_late.append((val_relate + deadzone + testzone, val_relate + deadzone + testzone, True)) list_case_normal.append((val_min - testzone, val_min - testzone, False)) list_case_normal.append((val_min, val_min, True)) list_case_normal.append((val_min + testzone, val_min + testzone, True)) list_case_normal.append((val_max + testzone, val_max + testzone, False)) list_case_normal.append((val_max, val_max, True)) list_case_normal.append((val_max - testzone, val_max - testzone, True)) print(f"Param Case:\taddr={display_hex(addr_param)}") pass def test_communication(device: DeviceSerial, time_out=2): """ 通信成功率测试 """ time_start = time.time() param_saved = device.flag_print, device.retry, device.time_out device.flag_print = False device.retry = 1 try: while True: device.frame_read(0x0C, 0x20) print(f"Time Stamp: {time.ctime()}") print(f"Success Frame: {device.log['read']}") print(f"Failed Frame: {device.log['send'] - device.log['read']}") print(f"Max Series Failed Frame: {device.log['keep-fail']}") time.sleep(time_out) finally: time_end = time.time() print("Test Result: ") print(f"Time Start: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_start))}, \tTime End: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_end))}") print(f"Time Elapsed: {time_end - time_start}") print(f"Success Rate: {device.log['read'] / device.log['send'] * 100}%") device.flag_print, device.retry, device.time_out = param_saved @pytest.fixture def device(): mode_config = { "Debug": {'com_name': 'COM3', 'frame_print': None, 'time_out': 0.5, 'retry': 3}, } device = LaminaAdapter(**mode_config['Debug']) yield device device.close_connection() @pytest.mark.parametrize("address, case_value", *TestCase_Adapter.items()) def test_parameters(device:DeviceSerial, address, itemCase): """ 参数读写测试 """ addr_relate = None itemCase = ParamCase[addr_param] list_case_normal = [] if 0 in itemCase.keys(): """ 常规读写用例测试 """ for data_write, data_read in itemCase[0]: list_case_normal.append((data_write, data_read, True)) if 1 in itemCase.keys(): """ 写入范围用例测试 """ testzone = 0.7 if info_param[1] in [2, 3] and len(info_param) < 3 else 7 / info_param[2] accuracy = 0.20001 if info_param[1] in [2, 3] and len(info_param) < 3 else 2.0001 / info_param[2] val_min, val_max = itemCase[1] if 2 in itemCase.keys(): """ 存在大小约束相关项 """ list_case_relate = [] # 对约束相关项的修改 list_case_late = [] # 存在约束相关项影响后的测试用例 mode_relate, addr_relate, deadzone = itemCase[2] val_relate = frame_read(device, addr_relate, ParamCase[addr_relate]) if mode_relate == 1: if (val_relate - deadzone) < val_max: """ 约束项已限制写入范围 """ list_case_relate.append((val_max + 2 * abs(deadzone), val_max + 2 * abs(deadzone), True)) list_case_late.append((val_max + testzone, val_max + testzone, False)) list_case_late.append((val_max, val_max, True)) list_case_late.append((val_max - testzone, val_max - testzone, True)) val_max = val_relate - deadzone else: """ 约束项未限制写入范围 """ val_relate = val_max list_case_relate.append((val_relate, val_relate, True)) list_case_late.append((val_relate - deadzone + testzone, val_relate - deadzone + testzone, False)) list_case_late.append((val_relate - deadzone, val_relate - deadzone, True)) list_case_late.append((val_relate - deadzone - testzone, val_relate - deadzone - testzone, True)) elif mode_relate == 2: if (val_relate + deadzone) > val_min: """ 约束项已限制写入范围 """ list_case_relate.append((val_min - 2 * abs(deadzone), val_min - 2 * abs(deadzone), True)) list_case_late.append((val_min - testzone, val_min - testzone, False)) list_case_late.append((val_min, val_min, True)) list_case_late.append((val_min + testzone, val_min + testzone, True)) val_min = val_relate + deadzone else: """ 约束项未限制写入范围 """ val_relate = val_min list_case_relate.append((val_relate, val_relate, True)) list_case_late.append((val_relate + deadzone - testzone, val_relate + deadzone - testzone, False)) list_case_late.append((val_relate + deadzone, val_relate + deadzone, True)) list_case_late.append((val_relate + deadzone + testzone, val_relate + deadzone + testzone, True)) list_case_normal.append((val_min - testzone, val_min - testzone, False)) list_case_normal.append((val_min, val_min, True)) list_case_normal.append((val_min + testzone, val_min + testzone, True)) list_case_normal.append((val_max + testzone, val_max + testzone, False)) list_case_normal.append((val_max, val_max, True)) list_case_normal.append((val_max - testzone, val_max - testzone, True)) print(f"Param Case:\taddr={display_hex(addr_param)}") last_value = frame_read(device, addr_param, info_param) for case_test in list_case_normal: print(f"\tnormal case={case_test}") result = frame_write(device, addr_param, info_param, case_test[0]) assert result == case_test[2] current_value = frame_read(device, addr_param, info_param) if current_value is None: raise Exception("Param Read Fail") elif result: if type(current_value) is float: if abs(current_value - case_test[1]) > accuracy: raise Exception("Param Check Fail") else: if current_value != case_test[1]: raise Exception("Param Check Fail") elif (not result) and current_value != last_value: raise Exception("Param Check Fail") last_value = current_value if list_case_normal and list_case_normal[0][1] != last_value: """ 为参数写入首个测试用例数据(一般为参数默认值), 避免影响后续参数测试 """ case_test = list_case_normal[0] result = frame_write(device, addr_param, info_param, case_test[0]) assert result == case_test[2] last_value = frame_read(device, addr_param, info_param) if addr_relate: """ 存在关联测试项 """ for case_relate in list_case_relate: print(f"\trelate state: addr={display_hex(addr_relate)}, value={case_relate[0]}") result = frame_write(device, addr_relate, info_param, case_relate[0]) if result == case_relate[2]: for case_test in list_case_late: print(f"\t\tstate case={case_test}") result = frame_write(device, addr_param, info_param, case_test[0]) assert result == case_test[2] current_value = frame_read(device, addr_param, info_param) if current_value is None: raise Exception("Param Read Fail") elif result: if type(current_value) is float: if abs(current_value - case_test[1]) > accuracy: raise Exception("Param Check Fail") else: if current_value != case_test[1]: raise Exception("Param Check Fail") elif (not result) and current_value != last_value: raise Exception("Param Check Fail") last_value = current_value if list_case_normal and list_case_normal[0][1] != last_value: """ 为参数写入首个测试用例数据(一般为参数默认值), 避免影响后续参数测试 """ case_test = list_case_normal[0] result = frame_write(device, addr_param, info_param, case_test[0]) assert result == case_test[2] def main(): mode_config = { "Log": {'com_name': None, # 'addr_645': [0x01, 0x00, 0x00, 0x00, 0x00, 0x40], }, "Debug": {'com_name': 'COM3', 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06], 'frame_print': True, 'time_out': 0.5, 'retry': 3}, } dev_lamina = LaminaAdapter(**mode_config['Debug']) test_parameters(dev_lamina, dev_lamina.block['data']['data_define'], TestCase) if __name__== "__main__": pytest.main()