本次流水线继电器一拖多资源支撑脚本初衷是为了节约成本,实现手机与继电器串口绑定。在实验过程中由于硬件不适配,处处碰壁,也是我第一次接触硬件方面的内容在此做些总结与记录。(由于部分内容不太清楚是否触及公司的保密协议,可能会打码)
关于继电器的说明:USB继电器模块搭载性能稳定的USB转串口芯片和微控制器,可在电脑端用串口调试软件发送串口指令来控制继电器的开和关。
方案:
- 1个手机链接1个双通道继电器,分别切换USB和供电,为1组继电器 (绑定手机与继电器组映射关系o轮询后映射)
- A手机测试时,BCD..手机供电继电器断开,USB切换继电器保持,BCD..手机无法通信,BCD不在线,
- 无手机测试时,所有设备供电,USB均可通信,LDMS中设备均在线
- 多个项目在同1个节点,当前任务A独占pc设备 (不能因手机断线而直接导致BCD排队中的任务失败),其余任务需排队
继电器的串口1中usb连接电脑,typc连接手机(故定义为通信串口),串口2连接电源充当供电串口。
代码案例如下:(暂未优化,还需添加当前继电器状态的定义,以及每次切换开关时回显转换前与转换后的状态对比等等。。。。)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# coding=utf-8
import logging
import sys
import time
import serial
from serial.tools import list_ports
import os
class _RelayChannel:
"""T20双通道USB继电器通道名称定义
"""
CONSTANT_PART = "A0" # 常量
lineone = "01" # 第一路
linetwo = "02" # 第二路
lineall = "0F" # 所有路
switchon = "01" # 打开
switchoff = "00" # 关闭
switchcheck = "02" # 查询
"打开第一路"
oneon = int(CONSTANT_PART, 16) + int(lineone, 16) + int(switchon, 16)
one_on = bytes.fromhex(CONSTANT_PART + lineone + switchon + f"{oneon:02X}")
"关闭第一路"
oneoff = int(CONSTANT_PART, 16) + int(lineone, 16) + int(switchoff, 16)
one_off = bytes.fromhex(CONSTANT_PART + lineone + switchoff + f"{oneoff:02X}")
"打开第二路"
twoon = int(CONSTANT_PART, 16) + int(linetwo, 16) + int(switchon, 16)
two_on = bytes.fromhex(CONSTANT_PART + linetwo + switchon + f"{twoon:02X}")
"关闭第二路"
twooff = int(CONSTANT_PART, 16) + int(linetwo, 16) + int(switchoff, 16)
two_off = bytes.fromhex(CONSTANT_PART + linetwo + switchoff + f"{twooff:02X}")
"打开所有路"
allon = int(CONSTANT_PART, 16) + int(lineall, 16) + int(switchon, 16)
all_on = bytes.fromhex(CONSTANT_PART + lineall + switchon + f"{allon:02X}")
"关闭所有路"
alloff = int(CONSTANT_PART, 16) + int(lineall, 16) + int(switchoff, 16)
all_off = bytes.fromhex(CONSTANT_PART + lineall + switchoff + f"{alloff:02X}")
"查询第一路状态"
checkone = int(CONSTANT_PART, 16) + int(lineone, 16) + int(switchcheck, 16)
check_one = bytes.fromhex(CONSTANT_PART + lineone + switchcheck + f"{checkone:02X}")
"查询第二路状态"
checktwo = int(CONSTANT_PART, 16) + int(linetwo, 16) + int(switchcheck, 16)
check_two = bytes.fromhex(CONSTANT_PART + linetwo + switchcheck + f"{checktwo:02X}")
"查询所有开关状态"
checkall = int(CONSTANT_PART, 16) + int(lineall, 16) + int(switchcheck, 16)
check_all = bytes.fromhex(CONSTANT_PART + lineall + switchcheck + f"{checkall:02X}")
class Relay(_RelayChannel):
def alloff(self): # 关闭所有开关
self.write_data(_RelayChannel.all_off)
def poweron(self): # 打开继电器
self.write_data(_RelayChannel.one_on)
def poweroff(self): # 关闭继电器
self.write_data(_RelayChannel.one_off)
def usbon(self): # 打开usb
self.write_data(_RelayChannel.two_on)
def usboff(self): # 关闭usb
self.write_data(_RelayChannel.two_off)
def allon(self): # 打开所有开关
self.write_data(_RelayChannel.all_on)
def querypower(self): # 查询供电状态
self.write_data(_RelayChannel.check_one)
pass
def queryusb(self): # 查询usb状态
self.serial.write(_RelayChannel.check_two)
pass
def queryall(self): # 查询所有状态
self.serial.write(_RelayChannel.check_all)
pass
class FuncRelayChannel(_RelayChannel):
"""功能通道映射关系
"""
"""查询是否存在DSTUR-T20双通道USB继电器
"""
ports = serial.tools.list_ports.comports()
def __init__(self, port: str = None, baudrate: int = 115200, bytesize: int = 8, timeout: float = 0.1):
"""
初始化串口通信对象。
:param port: 串口名称,如 'COM3' 或 '/dev/ttyUSB0'
:param baudrate: 波特率,默认为 115200
:param bytesize: 数据位,默认为 8 (常用值:7, 8)
:param timeout: 串口超时时间,默认为 0.1 秒
"""
self.port = port
self.baudrate = baudrate
self.bytesize = bytesize
self.timeout = timeout
self.serial_ports = [] # 串口列表
self.adb_device_list = [] # adb设备列表
self.serial_adb = {} # 手机与串口绑定关系列表
self._initialize_ports() # 初始化并打开所有串口
self.inquiry_serialPort() # 识别是否为指定串口
self.inquiry_adb() # 识别adb设备
self.close_and_reopen_ports() # 绑定设备
# self.check_power()
def _initialize_ports(self):
"""
等待测试模式(测试结束和测试前应该进入的模式)
初始化所有识别到的串口并打开。
"""
for port_info in FuncRelayChannel.ports:
try:
serial_connection = serial.Serial(port_info.device, self.baudrate, bytesize=self.bytesize,
timeout=self.timeout)
serial_connection.write(_RelayChannel.all_on)
# print(f"串口 {port_info.device} 已初始化并打开。")
except serial.SerialException as e:
print(f"串口初始化失败{e}")
def inquiry_serialPort(self):
"""
识别是否为指定串口设备
"""
# 存储串口列表
self.serial_ports = []
# 指定厂商ID
vid = 0x0588
# 指定设备ID
pid = 0x6470
for port_info in serial.tools.list_ports.comports():
# 检查设备是否匹配指定的VID和PID
if hasattr(port_info, 'vid') and hasattr(port_info, 'pid'):
if port_info.vid == vid and port_info.pid == pid:
self.serial_ports.append((port_info.device))
print(f"当前指定的串口设备: {port_info.device},VID={hex(port_info.vid)}, PID={hex(port_info.pid)}")
print()
else:
return False
# print("暂未发现其余指定串口设备。")
def inquiry_adb(self):
"""
识别所有的adb设备
"""
time.sleep(2)
# 执行adb devices命令并获取输出
self.adb_output = os.popen('adb devices').read()
# 存储安卓设备名称(SN号码)
self.adb_device_list = [line.split('\t')[0] for line in self.adb_output.splitlines()[1:] if 'device' in line]
# print(self.adb_device_list)
return set(self.adb_device_list) if self.adb_device_list else None
def close_and_reopen_ports(self):
"""
轮询串口
关闭一个串口,检查ADB设备,然后重新打开所有串口。(即绑定串口与设备)
"""
for port in self.serial_ports:
try:
adb_list = set(self.adb_device_list)
# print(f"正在关闭串口: {port}")
serial_connection = serial.Serial(port, self.baudrate, timeout=self.timeout)
serial_connection.write(_RelayChannel.two_off)
serial_connection.close()
# print(f"串口 {port} 已关闭")
self.inquiry_adb()
inquiry_adbs = self.inquiry_adb()
if inquiry_adbs is not None:
current_adb_devices = set(inquiry_adbs) # 更新ADB设备列表
missing_adb = list(adb_list - current_adb_devices)
if len(missing_adb) > 1:
print(f"串口 {port} 匹配到多个手机: {missing_adb}")
elif len(missing_adb) == 0:
print(f"串口 {port} 未匹配到手机设备")
else:
print(f"串口 {port} 匹配到单个手机: {missing_adb}")
phone_sn = missing_adb.pop()
self.serial_adb.update({port: phone_sn})
else:
print("inquiry_adbs 返回了 None,请查看是否已连接adb设备")
print(f'继电器与手机绑定关系:{self.serial_adb}')
# 重新打开所有串口
print("正在重新打开所有通信串口...")
self.close_all()
self._initialize_ports()
time.sleep(2)
self.inquiry_serialPort()
self.inquiry_adb()
print("所有串口已重新打开。")
except serial.SerialException as e:
print(f"{port}: {e}")
print()
def close_all(self):
"""
关闭所有已打开的通信串口。
"""
for port in self.serial_ports:
try:
serial_connection = serial.Serial(port, self.baudrate, bytesize=self.bytesize,
timeout=self.timeout)
serial_connection.write(_RelayChannel.two_off)
# print(f"串口 {port} 已关闭。")
except serial.SerialException as e:
print(f"无法关闭串口 {port}: {e}")
def alloff(self): # 关闭所有开关
self.serial.write(_RelayChannel.all_off)
def poweron(self): # 打开继电器
self.serial.write(_RelayChannel.one_on)
def poweroff(self): # 关闭继电器
self.serial.write(_RelayChannel.one_off)
def usbon(self): # 打开usb
self.serial.write(_RelayChannel.two_on)
def usboff(self): # 关闭usb
self.serial.write(_RelayChannel.two_off)
def allon(self): # 打开所有开关
self.serial.write(_RelayChannel.all_on)
def querypower(self): # 查询供电状态
self.serial.write(_RelayChannel.check_one)
pass
def queryusb(self): # 查询usb状态
self.serial.write(_RelayChannel.check_two)
pass
def queryall(self): # 查询所有状态
self.serial.write(_RelayChannel.check_all)
pass
class BusinessFunctions():
"""
具体业务功能映射关系
"""
excluded_port = 'COM5' # 指定独活串口
# 测试串口独活
def liveAlone(self):
# 列出所有可用的串口
ports = FuncRelayChannel.ports
self.channel = FuncRelayChannel()
serial.Serial
time.sleep(1)
# 遍历所有串口
for port, desc, hwid in ports:
if port == self.excluded_port:
# 如果是指定的排除串口,则跳过
continue
try:
# 尝试打开串口
with serial.Serial(port, 115200, timeout=1) as ser:
# 写入数据
self.channel.serial = ser
self.channel.usbon()
time.sleep(0.1)
self.channel.usboff()
# print(f'Written to {port}')
except serial.SerialException as e:
# 处理串口错误,例如串口被占用或不存在
print(f'Error writing to {port}: {e},处理串口错误,可能该串口不属于指定厂商或串口被占用或不存在')
def restoreInitialization(self): # 恢复初始化
self.channel._initialize_ports()
if __name__ == '__main__':
business = BusinessFunctions()
business.liveAlone() # 独活
time.sleep(2)
business.restoreInitialization() # 恢复初始化