文章目录
前言
一、程控方案的架构
二、实现过程
1.python实现
2.python实现心路历程
3.CAPL代码实现
总结
前言
相信做过车载测试的对于程控电源都不陌生,无论是诊断中对于重启测试,还是上电后默认会话的验证,以及ECU上下电,启动时间等测试,都需要通过CAPL去控制程控电源进行即时的电源通断和电压调整,本篇文章主要针对的是基于TCP网络协议通过Python直接控制程控电源,然后与CAPL通信解决方案的探讨,对于CAPL中现有的RS232协议,本文不做过多探讨,CSDN有的是。
一、程控方案的架构
本次对于程控电源的控制,通过python编写TCP代码对N39400程控电源的开启、关闭、电压以及电源进行直接调整(可调整项都作为参数传入),然后通过程控电源查询指令间隔查询设置值与实际值是否满足,满足则返回一个结果,到此完整的python代码打包为一个exe包。
接下来通过CAPL语言自带的调用EXE的函数sysExec,进行传参和python代码的调用,间接控制程控电源。
最后在CAPL代码中通过查询python返回结果来确定设置值与实际相等。
至此一个完整的程控电源控制脉络形成,即CAPL(带参数)-python(直接控制并返回结果)-CAPL(查询结果)-结束。
二、实现过程
1.python实现
代码中主要包含方法包括,tcp连接建立与关闭、电源开关设置、电源电压及电流设置、当前电源电压回读、canoe系统变量赋值。
代码如下:
import socketimport sysimport argparseimport timeimport mathimport osfrom win32com.client import Dispatchclass canoe_communicate: def info_excute_status(): Capp = Dispatch("CANoe.Application") namespace_name = "powerSupply" variable_name = "excute_status" try: # 获取指定的命名空间和变量对象 sys_namespace = Capp.System.Namespaces(namespace_name) target_variable = sys_namespace.Variables(variable_name) # 3. 为变量的Value属性赋值,即可改变其值 target_variable.Value = 1 print(f"已将系统变量 {namespace_name}::{variable_name} 的值设置为: {target_variable.Value}") except Exception as e: print(f"操作失败: {e}")class N39400PowerSupply: def __init__(self, ip, port=7000, timeout=1): """ 初始化IP连接 :param ip: IP地址(如 '192.168.1.1') :param port: 端口号,默认5025 :param timeout: 超时时间(秒),默认1 """ try: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.settimeout(timeout) self.socket.connect((ip, port)) time.sleep(0.01) # 等待连接初始化 except Exception as e: print(f"ERROR: IP连接失败 - {e}") sys.exit(1) def set_power_state(self, state,voltage,channel=1): """ 设置电源开关状态 :param state: 0表示关闭电源,1表示开启电源 :return: 返回操作结果 """ try: if state == 0: self.socket.send(f"OUTP{channel}:MODE 0\r\n".encode()) time.sleep(0.01) self.socket.send(f"OUTP{channel}:ONOFF 0\r\n".encode()) # print("SUCCESS: 电源已关闭") return "SUCCESS: 电源已关闭" elif state == 1: self.socket.send(f"OUTP{channel}:MODE 0\r\n".encode()) time.sleep(0.01) self.socket.send(f"OUTP{channel}:ONOFF 1\r\n".encode()) # print("SUCCESS: 电源已开启") for attempt in range(200): time.sleep(0.01) if (self.read_volatageANDcurrent(channel,voltage,1024)): canoe_communicate.info_excute_status() print(f"SUCCESS: 通道{channel},电压已设置为 {voltage} V") return f"SUCCESS: 通道{channel},电压已设置为 {voltage} V" else: # print(f"ERROR: 设置电压失败") print("attempt count:",attempt) if (attempt > 199): return False return False # return "SUCCESS: 电源已开启" else: # print("ERROR: 无效的输入,请输入0或1") return "ERROR: 无效的输入,请输入0或1" except Exception as e: # print(f"ERROR: 设置电源状态失败 - {e}") return f"ERROR: 设置电源状态失败 - {e}" def set_voltageANDcurrent(self, voltage, current, channel=1): """ 设置输出电压电流 :param voltage: 目标电压值(程控电源允许的范围内) :param current: 目标电流值(程控电源允许的范围内) :return: 返回操作结果 """ try: self.socket.send(f"SOUR{channel}:VOLT {voltage}\r\n".encode()) time.sleep(0.01) self.socket.send(f"SOUR{channel}:CURR {current}\r\n".encode()) for attempt in range(200): time.sleep(0.01) if (self.read_volatageANDcurrent(channel,voltage,1024)): canoe_communicate.info_excute_status() print(f"SUCCESS: 通道{channel},电压已设置为 {voltage} V") return f"SUCCESS: 通道{channel},电压已设置为 {voltage} V,电流已设置为 {current} A" else: # print(f"ERROR: 设置电压失败") print("attempt count:",attempt) if (attempt > 199): return False return False except Exception as e: # print(f"ERROR: 设置电压失败 - {e}") return f"ERROR: 设置电压电流失败 - {e}" def read_volatageANDcurrent(self,channel,target_voltage,buffer_size=1024): """ 读取电压电流值 """ try: self.socket.send(f"MEASure{channel}:VOLTage?\r\n".encode()) read_voltage = self.socket.recv(buffer_size).decode('utf-8').strip() if math.isclose(float(read_voltage),target_voltage,rel_tol=1e-02): # print("电压与目标电压一致:",target_voltage,read_voltage) return True else: print("电压与目标电压不一致:",target_voltage,read_voltage) return False except Exception as e: # print(f"ERROR: 读取失败 - {e}") return f"ERROR: 读取失败 - {e}" def createFileWriteExcuteResult(self,status, directory_path="."): # 验证参数 if status not in [0, 1]: print("错误: 状态值必须是0或1") return False try: # 如果目录不存在,则创建 if not os.path.exists(directory_path): os.makedirs(directory_path, exist_ok=True) print(f"目录已创建: {directory_path}") # 构建完整文件路径 file_path = os.path.join(directory_path, "power_excute_status.txt") # 写入文件 with open(file_path, 'w') as f: f.write(f"excute_status:{status}\n") f.flush() # 强制将数据从内存缓冲区写入磁盘 os.fsync(f.fileno()) # 强制操作系统将缓存写入物理磁盘 # 检查文件是否为新创建 if os.path.exists(file_path): action = "更新" if os.path.getsize(file_path) > 0 else "创建并写入" else: action = "创建并写入" print(f"文件已{action}: {file_path},状态值: excute_status:{status}") return True except Exception as e: print(f"写入文件时出错: {e}") return False """ # 使用相对路径 write_execute_status(0, "status") # 使用绝对路径 write_execute_status(1, "/home/user/status") # 当前目录 write_execute_status(1) """ def close(self): """ 关闭Socket连接 """ self.socket.close()def parse_args(): """解析命令行参数""" parser = argparse.ArgumentParser(description="N39400程控电源控制(IP版)") parser.add_argument("--ip", type=str, required=True, help="IP地址(如 192.168.1.1)") parser.add_argument("--port", type=int, default=7000, help="端口号,默认7000") parser.add_argument("--channel", type=int, default=1, help="通道号,默认为1") parser.add_argument("--mode", type=int, required=True, help="控制模式: 1=电源开关, 2=电压电流调节") parser.add_argument("--value_voltage", type=str, required=True, help="控制值: 模式1为0/1, 模式2为电压值") parser.add_argument("--value_current", type=str, required=True, help="控制值: 模式2为电流值") return parser.parse_args()def main(): try: args = parse_args() ps = N39400PowerSupply(args.ip, args.port) if args.mode == 1: state = float(args.value_voltage) current = float(args.value_current) result = ps.set_power_state(state,current,args.channel) print(result) elif args.mode == 2: voltage = float(args.value_voltage) current = float(args.value_current) result = ps.set_voltageANDcurrent(voltage,current,args.channel) print(result) else: print("ERROR: 无效的模式编号,请输入1或2") except ValueError: print("ERROR: 无效的输入值") except Exception as e: print(f"ERROR: 程序异常 - {e}") finally: ps.close()if __name__ == "__main__": main()
2.python实现心路历程
从python代码中不难看出,对于执行结果的返回,我采取过文件读写的方式,即python查询设置电压值与实际值一致后会去指定目录生成一个文件,文件中写入key-value,capl通过文件读写去读取这个值,从而间接获得程控电源的执行结果,但是实际执行下来,发现文件读写侠侣很低,而且python中操作完文件无法及时的关闭文件锁,造成capl中读取完数值后无法将数值初始化,由于极低的数据传递效率,造成实际程控电源执行完成结果后与获得结果中间存在几百毫秒的延迟,这在车载测试中是无法接受的,10毫秒的延迟就可能错失一个信号的接收等,尤其在开机时间的测试中更是不能接受。
所以我觉得换一个思路,也就是python代码中体现的,通过python中的com库,直接操作已经运行起来的canoe实例,对当前canoe实例中的系统变量进行赋值,这样我只需要在canoe工程中添加一个系统变量,即实现了python执行结果直接给到canoe,不通过其他方式中转,极大提高了执行效率。
3.CAPL代码实现
CAPL代码实现比较简单,主要包含传入参数的整合以及exe文件调用,最后是执行结果查询
代码如下:
int power_control_handler(char address[],int channel, int control_mode, double value_voltage, double value_current){ //当设置模式为开机时,电流值需要与关机前或者关机后设置的电压值相等,因为会通过电流值传参比较获取开机时间 int res = 0; char parameter[500]; snprintf(parameter,elCount(parameter),"--ip \"%s\" --channel \"%d\" --mode \"%d\" --value_voltage \"%f\" --value_current \"%f\"",address,channel,control_mode,value_voltage,value_current); res = sysExec(n39400_excutor,parameter); if((value_voltage!=Npower_off) && (get_power_excute_status())){ return res; }else{ return res; } return 0;}int get_power_excute_status(){ dword count = 0; while(@sysvar::powerSupply::excute_status==0){ testWaitForTimeoutSilent(1); count++; if(count>9999){ write("power supply excute status get ERROR !"); count = 0; return 0; } } count = 0; return 1;}
总结
以上就是我对于程控电源的探索,通过这种方式,用着还是可以的。