- 读取 D700(阀后流量)、D710(阀后压力)、D720(阀前压力), 读用功能码 03,0~32000 线性映射到各自量程(300 SLM / 400 kPa / 300 kPa) - 输出 D730(电机位置,0~10V),写用功能码 06,行程仍为 0~1000、死区 800 - 复用 MT2 骨架,保留 get_flow/get_pressure/set_motor_position 接口, channel 参数改为直接 Modbus 地址 - config.py 新增 PLC 设置,保留 MT2AM8 设置为死代码(MT2AM8Client 类保留) - 同步更新 README.md 中的硬件接线、配置默认值与模块职责描述
723 lines
26 KiB
Python
723 lines
26 KiB
Python
import time
|
||
import os
|
||
import sys
|
||
from pymodbus.client import ModbusTcpClient, ModbusSerialClient
|
||
from pymodbus.exceptions import ModbusException
|
||
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
|
||
from pymodbus.constants import Endian
|
||
|
||
|
||
def _motor_log(msg: str):
|
||
"""电机操作日志,直接写文件 + 刷盘"""
|
||
try:
|
||
log_dir = os.path.join(os.path.dirname(sys.executable), "logs")
|
||
os.makedirs(log_dir, exist_ok=True)
|
||
log_file = os.path.join(log_dir, "control_debug.log")
|
||
with open(log_file, "a", encoding="utf-8") as f:
|
||
f.write(f"[{time.strftime('%H:%M:%S.%f')[:-3]}] [MOTOR] {msg}\n")
|
||
f.flush()
|
||
os.fsync(f.fileno())
|
||
except Exception:
|
||
pass
|
||
|
||
volthege_min = 819 # 模拟量映射最小值(对应 0V/4mA)—— MT2 旧用
|
||
volthege_max = 4095 # 模拟量映射最大值(对应 10V/20mA)—— MT2 旧用
|
||
x_max = 1000 # 电机最大行程(PLC 沿用 0~1000 坐标,死区由 config 给定)
|
||
raw_max = 32000 # PLC 模拟量映射最大值(0~32000,对应量程满量程)
|
||
impulse_max = 163840 # 电机脉冲最大值(对应 x_max)
|
||
|
||
|
||
def set_motor_limits(volthege_min_val=None, volthege_max_val=None, x_max_val=None):
|
||
"""更新电机限幅参数(由 GUI 高级设置页面调用)
|
||
|
||
Args:
|
||
volthege_min_val: 模拟量映射最小值,None 表示不更新
|
||
volthege_max_val: 模拟量映射最大值,None 表示不更新
|
||
x_max_val: 最大行程(对应 GUI 总限幅),None 表示不更新
|
||
"""
|
||
global volthege_min, volthege_max, x_max
|
||
if volthege_min_val is not None:
|
||
volthege_min = volthege_min_val
|
||
if volthege_max_val is not None:
|
||
volthege_max = volthege_max_val
|
||
if x_max_val is not None:
|
||
x_max = x_max_val
|
||
|
||
# ---------- PLC(Easy521)Modbus TCP 客户端 ----------
|
||
class Easy521ModbusClient:
|
||
"""
|
||
信捷 Easy521 PLC 的 Modbus TCP 通讯类。
|
||
|
||
- D 寄存器为直接 Modbus 地址:读用功能码 03(保持寄存器),写用功能码 06。
|
||
- 模拟量映射:0~raw_max(32000)线性对应各量程满量程。
|
||
- 电机行程仍沿用 0~x_max(1000)坐标(死区由 config 给定),映射到 0~raw_max 输出。
|
||
|
||
对外接口沿用 MT2AM8Client 的 get_flow / get_pressure / set_motor_position,
|
||
只是这里的 ``channel`` 参数是寄存器地址。
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
host="192.168.1.88",
|
||
port=502,
|
||
slave_id=1,
|
||
flow_addr=700,
|
||
flow_range=300.0,
|
||
pressure_before_addr=720,
|
||
pressure_before_range=300.0,
|
||
pressure_after_addr=710,
|
||
pressure_after_range=400.0,
|
||
motor_output_addr=730,
|
||
):
|
||
self.host = host
|
||
self.port = port
|
||
self.slave_id = slave_id
|
||
self.flow_addr = int(flow_addr)
|
||
self.flow_range = float(flow_range)
|
||
self.pressure_before_addr = int(pressure_before_addr)
|
||
self.pressure_before_range = float(pressure_before_range)
|
||
self.pressure_after_addr = int(pressure_after_addr)
|
||
self.pressure_after_range = float(pressure_after_range)
|
||
self.motor_output_addr = int(motor_output_addr)
|
||
self.client = ModbusTcpClient(
|
||
host=host,
|
||
port=port,
|
||
timeout=3,
|
||
retries=3,
|
||
)
|
||
self.connected = False
|
||
|
||
def connect(self):
|
||
try:
|
||
conn = self.client.connect()
|
||
if conn:
|
||
print(f"成功连接到 PLC {self.host}:{self.port}")
|
||
self.connected = True
|
||
else:
|
||
print(f"无法连接到 PLC {self.host}:{self.port}")
|
||
self.connected = False
|
||
return conn
|
||
except Exception as e:
|
||
print(f"连接错误: {e}")
|
||
self.connected = False
|
||
return False
|
||
|
||
def disconnect(self):
|
||
self.client.close()
|
||
self.connected = False
|
||
print("连接已关闭")
|
||
|
||
def _read_register(self, address):
|
||
"""读单个 16 位保持寄存器(功能码 03),返回原始整数,失败返回 None。"""
|
||
try:
|
||
result = self.client.read_holding_registers(
|
||
address=int(address),
|
||
count=1,
|
||
slave=self.slave_id,
|
||
)
|
||
if not result.isError():
|
||
return result.registers[0]
|
||
print(f"读取寄存器 D{int(address)} 错误: {result}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"读取寄存器 D{int(address)} 异常: {e}")
|
||
return None
|
||
|
||
def _write_register(self, address, value):
|
||
"""写单个 16 位保持寄存器(功能码 06)。"""
|
||
try:
|
||
result = self.client.write_register(
|
||
address=int(address),
|
||
value=int(value),
|
||
slave=self.slave_id,
|
||
)
|
||
if result.isError():
|
||
print(f"写入寄存器 D{int(address)} 错误: {result}")
|
||
return False
|
||
return True
|
||
except Exception as e:
|
||
print(f"写入寄存器 D{int(address)} 异常: {e}")
|
||
return False
|
||
|
||
def get_flow(self, address):
|
||
"""读阀后流量,raw / raw_max * flow_range -> SLM。"""
|
||
raw = self._read_register(address)
|
||
if raw is None:
|
||
return None
|
||
return raw / raw_max * self.flow_range
|
||
|
||
def get_pressure(self, address):
|
||
"""读压力,按寄存器地址匹配量程(阀前 300 / 阀后 400)-> kPa。"""
|
||
address = int(address)
|
||
if address == self.pressure_before_addr:
|
||
rng = self.pressure_before_range
|
||
elif address == self.pressure_after_addr:
|
||
rng = self.pressure_after_range
|
||
else:
|
||
print(f"未知压力寄存器 D{address},按阀后量程兜底")
|
||
rng = self.pressure_after_range
|
||
raw = self._read_register(address)
|
||
if raw is None:
|
||
return None
|
||
return raw / raw_max * rng
|
||
|
||
def set_motor_position(self, position, channel=None):
|
||
"""把 0~x_max 的行程映射到 0~raw_max 后写入电机输出寄存器。"""
|
||
if channel is None:
|
||
channel = self.motor_output_addr
|
||
position = float(position)
|
||
if not 0 <= position <= x_max:
|
||
print(f"行程需在 0~{x_max:.0f} 之间")
|
||
return False
|
||
raw_value = int(position / x_max * raw_max)
|
||
return self._write_register(channel, raw_value)
|
||
|
||
|
||
# ---------- 新增:独立的电机 Modbus RTU 客户端类(含报文打印) ----------
|
||
class MotorModbusRTUClient:
|
||
"""电机 Modbus RTU 通讯客户端(增强调试报文打印)"""
|
||
# 参数来源(GUI 页面1 Modbus RTU 区):
|
||
# port <- 端口号 (下拉)
|
||
# baudrate <- 波特率 (默认 115200)
|
||
# slave_id <- 站号 (默认 4)
|
||
# bytesize <- 数据位 (默认 8)
|
||
# stopbits <- 停止位 (默认 1)
|
||
# parity <- 校验位 None/Odd/Even -> 'N'/'O'/'E' (默认 'N')
|
||
def __init__(self, port='/dev/cu.usbserial-BG02B0IX', slave_id=4, baudrate=115200,
|
||
bytesize=8, parity='N', stopbits=1):
|
||
# def __init__(self, port='/dev/cu.usbserial-D30JITMY', slave_id=4, baudrate=115200):
|
||
self.port = port
|
||
self.slave_id = slave_id
|
||
self.baudrate = baudrate
|
||
self.bytesize = bytesize
|
||
self.parity = parity
|
||
self.stopbits = stopbits
|
||
self.client = None
|
||
|
||
def connect(self):
|
||
"""连接电机串口"""
|
||
self.client = ModbusSerialClient(
|
||
port=self.port,
|
||
baudrate=self.baudrate,
|
||
bytesize=self.bytesize,
|
||
parity=self.parity,
|
||
stopbits=self.stopbits,
|
||
timeout=5 # 超时时间延长,便于观察
|
||
)
|
||
if self.client.connect():
|
||
print(f"电机串口 {self.port} 连接成功")
|
||
return True
|
||
else:
|
||
print(f"电机串口 {self.port} 连接失败")
|
||
return False
|
||
|
||
def disconnect(self):
|
||
"""断开电机串口"""
|
||
if self.client:
|
||
self.client.close()
|
||
self.client = None
|
||
print("电机串口已关闭")
|
||
|
||
@staticmethod
|
||
def _compute_crc(data: bytes) -> bytes:
|
||
"""计算 Modbus CRC-16"""
|
||
crc = 0xFFFF
|
||
for byte in data:
|
||
crc ^= byte
|
||
for _ in range(8):
|
||
if crc & 1:
|
||
crc = (crc >> 1) ^ 0xA001
|
||
else:
|
||
crc >>= 1
|
||
return crc.to_bytes(2, byteorder='little')
|
||
|
||
def _print_sent_message(self, address, function_code, data_bytes):
|
||
"""构造完整报文并打印(含CRC)"""
|
||
raw = bytes([self.slave_id, function_code]) + address.to_bytes(2, byteorder='big') + data_bytes
|
||
crc = self._compute_crc(raw)
|
||
full_msg = raw + crc
|
||
hex_str = ' '.join(f'{b:02X}' for b in full_msg)
|
||
# print(f"[发送] {hex_str}")
|
||
|
||
def _write_single_register(self, address, value):
|
||
"""写单个寄存器(功能码 06)"""
|
||
data_bytes = value.to_bytes(2, byteorder='big')
|
||
self._print_sent_message(address, 0x06, data_bytes)
|
||
|
||
try:
|
||
result = self.client.write_register(address, value, slave=self.slave_id)
|
||
if result.isError():
|
||
print(f"[接收] 错误响应: {result}")
|
||
return False
|
||
else:
|
||
# print(f"[接收] 成功")
|
||
return True
|
||
except Exception as e:
|
||
print(f"[接收] 异常: {e}")
|
||
return False
|
||
|
||
def _write_32bit(self, address, value):
|
||
"""
|
||
写 32 位值到两个连续寄存器(功能码 10)
|
||
字节序:大端(高字节在前,高字在前)
|
||
"""
|
||
builder = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.BIG)
|
||
builder.add_32bit_uint(value)
|
||
payload = builder.to_registers()
|
||
# 构造数据部分:字节计数 + 各寄存器大端两字节
|
||
data_bytes = bytes([len(payload) * 2])
|
||
for reg in payload:
|
||
data_bytes += reg.to_bytes(2, byteorder='big')
|
||
self._print_sent_message(address, 0x10, data_bytes)
|
||
|
||
try:
|
||
result = self.client.write_registers(address, payload, slave=self.slave_id)
|
||
if result.isError():
|
||
print(f"[接收] 错误响应: {result}")
|
||
return False
|
||
else:
|
||
# print(f"[接收] 成功")
|
||
return True
|
||
except Exception as e:
|
||
print(f"[接收] 异常: {e}")
|
||
return False
|
||
|
||
def init(self):
|
||
"""初始化电机参数(仅需调用一次)"""
|
||
if not self.client or not self.client.connected:
|
||
print("电机未连接,请先调用 connect()")
|
||
return False
|
||
|
||
print("开始初始化电机参数...")
|
||
success = True
|
||
|
||
# 1. 写模式 4 → 0x6007
|
||
print("--- 步骤1: 写模式 4 到 0x6007 ---")
|
||
if not self._write_single_register(0x6007, 4):
|
||
success = False
|
||
print("模式写入失败")
|
||
else:
|
||
print("模式写入成功")
|
||
|
||
# 2. 写速度 64000 → 0x6072
|
||
print("--- 步骤2: 写速度 64000 到 0x6072 ---")
|
||
if not self._write_32bit(0x6072, 64000):
|
||
success = False
|
||
print("速度写入失败")
|
||
else:
|
||
print("速度写入成功")
|
||
|
||
# 3. 写加速度 2400000 → 0x6067
|
||
print("--- 步骤3: 写加速度 96000 到 0x6067 ---")
|
||
if not self._write_32bit(0x6067, 96000):
|
||
success = False
|
||
print("加速度写入失败")
|
||
else:
|
||
print("加速度写入成功")
|
||
|
||
# 4. 写减速度 240000 → 0x6069
|
||
print("--- 步骤4: 写减速度 96000 到 0x6069 ---")
|
||
if not self._write_32bit(0x6069, 96000):
|
||
success = False
|
||
print("减速度写入失败")
|
||
else:
|
||
print("减速度写入成功")
|
||
|
||
if success:
|
||
print("电机初始化完成。")
|
||
else:
|
||
print("电机初始化过程中出现错误。")
|
||
return success
|
||
|
||
def _read_single_register(self, address):
|
||
"""读取单个16位寄存器"""
|
||
try:
|
||
result = self.client.read_holding_registers(address, 1, slave=self.slave_id)
|
||
if not result.isError():
|
||
return result.registers[0]
|
||
else:
|
||
print(f"读取寄存器 0x{address:X} 失败: {result}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"读取寄存器 0x{address:X} 异常: {e}")
|
||
return None
|
||
|
||
def set_position(self, position):
|
||
"""设置目标位置并立即启动(位置 0~impulse_max"""
|
||
if not self.client or not self.client.connected:
|
||
print("电机未连接,请先调用 connect()")
|
||
return False
|
||
|
||
position = int(position / x_max * impulse_max)
|
||
|
||
if not (0 <= position <= impulse_max):
|
||
print(f"位置值 {position} 超出范围 (0~{impulse_max})")
|
||
return False
|
||
|
||
self._write_32bit(0x6074, position)
|
||
ret2 = self._write_single_register(0x6070, 112)
|
||
if not ret2:
|
||
print("第一次写入控制字失败,1ms后重试...")
|
||
time.sleep(0.001)
|
||
ret2 = self._write_single_register(0x6070, 112)
|
||
if ret2:
|
||
print("第二次重试成功")
|
||
else:
|
||
print("第二次重试仍然失败")
|
||
|
||
return True
|
||
|
||
def read_current_position(self):
|
||
"""
|
||
读取电机当前位置(INT 型,32位有符号整数)
|
||
从寄存器 0x600E 开始,连续读取 2 个保持寄存器
|
||
字节序:大端(与写操作一致)
|
||
:return: 当前位置值(int),读取失败返回 None
|
||
"""
|
||
if not self.client or not self.client.connected:
|
||
print("电机未连接,无法读取位置")
|
||
return None
|
||
|
||
try:
|
||
result = self.client.read_holding_registers(
|
||
address=0x600E,
|
||
count=2,
|
||
slave=self.slave_id
|
||
)
|
||
if result.isError():
|
||
print(f"读取位置寄存器失败: {result}")
|
||
return None
|
||
|
||
# 解码为 32 位有符号整数,使用与写操作相同的大端字节序
|
||
decoder = BinaryPayloadDecoder.fromRegisters(
|
||
result.registers,
|
||
byteorder=Endian.BIG,
|
||
wordorder=Endian.BIG
|
||
)
|
||
position = decoder.decode_32bit_int()
|
||
print(f"当前位置position: {position}")
|
||
position_x = position / impulse_max * x_max
|
||
return position_x
|
||
except Exception as e:
|
||
print(f"读取当前位置异常: {e}")
|
||
return None
|
||
|
||
|
||
# ---------- PC读取压力值 ----------
|
||
class PressureModbusRTUClient:
|
||
"""压力变送器 Modbus RTU 通讯客户端(读取16位压力值)"""
|
||
def __init__(self, port='/dev/cu.usbserial-D30JITMY', slave_id=1, baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=3):
|
||
"""
|
||
初始化压力客户端
|
||
:param port: 串口端口,如 COM3、/dev/ttyUSB0
|
||
:param slave_id: 从站地址(站号),默认 1
|
||
:param baudrate: 波特率,默认 9600
|
||
:param bytesize: 数据位,默认 8
|
||
:param parity: 校验位,默认 'N'(无校验)
|
||
:param stopbits: 停止位,默认 1
|
||
:param timeout: 通讯超时时间(秒),默认 3
|
||
"""
|
||
self.port = port
|
||
self.slave_id = slave_id
|
||
self.baudrate = baudrate
|
||
self.bytesize = bytesize
|
||
self.parity = parity
|
||
self.stopbits = stopbits
|
||
self.timeout = timeout
|
||
self.client = None
|
||
self.pressure_register_addr = 4 # 压力寄存器地址(04)
|
||
|
||
def connect(self):
|
||
"""连接压力变送器串口"""
|
||
self.client = ModbusSerialClient(
|
||
port=self.port,
|
||
baudrate=self.baudrate,
|
||
bytesize=self.bytesize,
|
||
parity=self.parity,
|
||
stopbits=self.stopbits,
|
||
timeout=self.timeout
|
||
)
|
||
if self.client.connect():
|
||
print(f"压力串口 {self.port} 连接成功 (站号 {self.slave_id})")
|
||
return True
|
||
else:
|
||
print(f"压力串口 {self.port} 连接失败")
|
||
return False
|
||
|
||
def disconnect(self):
|
||
"""断开压力串口"""
|
||
if self.client:
|
||
self.client.close()
|
||
self.client = None
|
||
print("压力串口已关闭")
|
||
|
||
def get_current_p(self):
|
||
"""
|
||
读取压力值
|
||
:return: 压力值(整数),若读取失败返回 None
|
||
"""
|
||
if not self.client or not self.client.connected:
|
||
print("压力客户端未连接,请先调用 connect()")
|
||
return None
|
||
|
||
try:
|
||
t1 = time.perf_counter()
|
||
# 读取保持寄存器(功能码03),地址4,个数1
|
||
result = self.client.read_holding_registers(
|
||
address=self.pressure_register_addr,
|
||
count=1,
|
||
slave=self.slave_id
|
||
)
|
||
if result.isError():
|
||
print(f"压力读取错误: {result}")
|
||
return None
|
||
# 返回寄存器的第一个值(16位整数)
|
||
pressure_raw = result.registers[0]
|
||
# print(f"读压力用时:{time.perf_counter() - t1:.3f}s")
|
||
return pressure_raw
|
||
except ModbusException as e:
|
||
print(f"压力读取 Modbus 异常: {e}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"压力读取未知异常: {e}")
|
||
return None
|
||
|
||
|
||
# ---------- 新增:MT2-AM8 模块 Modbus TCP 客户端 ----------
|
||
class MT2AM8Client:
|
||
"""
|
||
艾莫迅 MT2-AM8 模块的 Modbus TCP 通讯类
|
||
- 默认 IP:192.168.1.12,端口 502,模块地址(站号)默认为 1
|
||
- 输入寄存器(AI):地址 0x00~0x03(对应 PLC 地址 30001~30004)
|
||
- 保持寄存器(AO):地址 0x00~0x03(对应 PLC 地址 40001~40004)
|
||
- 模拟量值范围:0~4095(对应 0~10V 或 0~20mA)
|
||
"""
|
||
def __init__(self, host="192.168.1.12", port=502, slave_id=1,
|
||
pressure_range=400, flow_range=300):
|
||
self.host = host
|
||
self.port = port
|
||
self.slave_id = slave_id
|
||
self.pressure_range = pressure_range # 压力表量程上限
|
||
self.flow_range = flow_range # 流量计量程上限
|
||
self.client = ModbusTcpClient(
|
||
host=host,
|
||
port=port,
|
||
timeout=3,
|
||
retries=3
|
||
)
|
||
self.connected = False
|
||
|
||
def connect(self):
|
||
"""连接模块"""
|
||
try:
|
||
conn = self.client.connect()
|
||
if conn:
|
||
print(f"成功连接到 MT2-AM8 模块 {self.host}:{self.port}")
|
||
self.connected = True
|
||
else:
|
||
print(f"无法连接到 {self.host}:{self.port}")
|
||
self.connected = False
|
||
return conn
|
||
except Exception as e:
|
||
print(f"连接错误: {e}")
|
||
self.connected = False
|
||
return False
|
||
|
||
def disconnect(self):
|
||
"""断开连接"""
|
||
self.client.close()
|
||
self.connected = False
|
||
print("连接已关闭")
|
||
|
||
def read_analog_input(self, channel):
|
||
"""
|
||
读取单路模拟量输入原始值(16位无符号整数)
|
||
:param channel: 通道号 0~3(对应 AI1~AI4)
|
||
:return: 0~4095 的整数值,失败返回 None
|
||
"""
|
||
try:
|
||
result = self.client.read_input_registers(
|
||
address=channel,
|
||
count=1,
|
||
slave=self.slave_id
|
||
)
|
||
if not result.isError():
|
||
return result.registers[0]
|
||
else:
|
||
print(f"读取输入寄存器错误: {result}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"读取模拟量输入异常: {e}")
|
||
return None
|
||
|
||
# def read_all_analog_inputs(self):
|
||
# """
|
||
# 一次性读取全部 4 路模拟量输入
|
||
# :return: 长度为4的列表(int),失败返回 None
|
||
# """
|
||
# try:
|
||
# result = self.client.read_input_registers(
|
||
# address=0,
|
||
# count=4,
|
||
# slave=self.slave_id
|
||
# )
|
||
# if not result.isError():
|
||
# return result.registers
|
||
# else:
|
||
# print(f"读取全部输入寄存器错误: {result}")
|
||
# return None
|
||
# except Exception as e:
|
||
# print(f"读取全部模拟量输入异常: {e}")
|
||
# return None
|
||
|
||
def write_analog_output(self, channel, value):
|
||
"""
|
||
写入单路模拟量输出(保持寄存器)
|
||
:param channel: 通道号 0~3(对应 AO1~AO4)
|
||
:param value: {volthege_min}~{volthege_max} 的整数值
|
||
:return: True 成功,False 失败
|
||
"""
|
||
# if not 0 <= channel <= 3:
|
||
# print("通道号必须为 0~3")
|
||
# return False
|
||
if not 0 <= value <= volthege_max:
|
||
print(f"值 {value} 超出范围 ({volthege_min}~{volthege_max})")
|
||
return False
|
||
try:
|
||
result = self.client.write_register(
|
||
address=channel,
|
||
value=value,
|
||
slave=self.slave_id
|
||
)
|
||
return not result.isError()
|
||
except Exception as e:
|
||
print(f"写入模拟量输出异常: {e}")
|
||
return False
|
||
|
||
# def write_all_analog_outputs(self, values):
|
||
# """
|
||
# 一次性写入全部 4 路模拟量输出(用于批量设置)
|
||
# :param values: 长度为4的列表或元组,每个元素为 0~4095
|
||
# :return: True 成功,False 失败
|
||
# """
|
||
# if len(values) != 4:
|
||
# print("需提供 4 个输出值")
|
||
# return False
|
||
# for v in values:
|
||
# if not 0 <= v <= 4095:
|
||
# print(f"值 {v} 超出范围 (0~4095)")
|
||
# return False
|
||
# try:
|
||
# result = self.client.write_registers(
|
||
# address=0,
|
||
# values=list(values),
|
||
# slave=self.slave_id
|
||
# )
|
||
# return not result.isError()
|
||
# except Exception as e:
|
||
# print(f"批量写入模拟量输出异常: {e}")
|
||
# return False
|
||
|
||
def get_pressure(self, channel):
|
||
"""
|
||
读取压力值并转换为实际物理量
|
||
转换公式:raw / (volthege_max - volthege_min) * pressure_range
|
||
:param channel: 压力传感器模拟量通道地址
|
||
:return: 实际压力值(kPa),失败返回 None
|
||
"""
|
||
raw = self.read_analog_input(channel)
|
||
if raw is None:
|
||
return None
|
||
pressure = (raw - volthege_min) / (volthege_max - volthege_min) * self.pressure_range
|
||
# print(f"读取压力通道 {channel} 原始值: {raw}, 转换后压力: {pressure:.2f} kPa")
|
||
return pressure
|
||
|
||
def get_flow(self, channel):
|
||
"""
|
||
读取流量值并转换为实际物理量
|
||
转换公式:raw / (volthege_max - volthege_min) * flow_range
|
||
:param channel: 流量计模拟量通道地址
|
||
:return: 实际流量值(L/min),失败返回 None
|
||
"""
|
||
raw = self.read_analog_input(channel)
|
||
if raw is None:
|
||
return None
|
||
flow = (raw - volthege_min) / (volthege_max - volthege_min) * self.flow_range
|
||
# print(f"读取流量通道 {channel} 原始值: {raw}, 转换后流量: {flow:.2f} L/min")
|
||
return flow
|
||
|
||
|
||
# def set_motor_speed(self, voltage_percent):
|
||
# """
|
||
# 通过模拟量输出控制电机(例如 0~100% 对应 0~10V)
|
||
# :param voltage_percent: 0~100 的浮点数,表示百分比
|
||
# """
|
||
# if not 0 <= voltage_percent <= 100:
|
||
# print("百分比需在 0~100 之间")
|
||
# return False
|
||
# # 将百分比映射到 0~4095
|
||
# raw_value = int(voltage_percent / 100.0 * 4095)
|
||
# return self.write_analog_output(0, raw_value) # 假设电机接在 AO1
|
||
|
||
def set_motor_position(self, voltage_distance, channel=0):
|
||
"""
|
||
通过模拟量输出控制电机(例如 0~1000 对应 0~10V)
|
||
:param voltage_distance: 0~1000 的浮点数,表示行程
|
||
:param channel: 模拟量输出通道号,默认 0(AO1)
|
||
"""
|
||
if not (0 <= voltage_distance <= x_max):
|
||
print(f"行程需在 0~{x_max} 之间")
|
||
return False
|
||
# 将行程映射到 0~4095
|
||
# Map the full travel range to the configured analog-output range.
|
||
# The lower endpoint must include volthege_min; otherwise position 0
|
||
# produces raw value 0 and is rejected by write_analog_output().
|
||
raw_value = int( voltage_distance / x_max * volthege_max )
|
||
# print(f"设置电机行程为 {voltage_distance},模拟量输出值 {raw_value}")
|
||
return self.write_analog_output(channel, raw_value)
|
||
|
||
|
||
# ---------- 主函数:测试示例 ----------
|
||
# if __name__ == "__main__":
|
||
# # (可选)启用 pymodbus 详细日志,可观察底层收发帧
|
||
# # logging.basicConfig()
|
||
# # logging.getLogger('pymodbus').setLevel(logging.DEBUG)
|
||
#
|
||
# motor = MotorModbusRTUClient()
|
||
#
|
||
#
|
||
# print("连接电机 (Modbus RTU)...")
|
||
# if not motor.connect():
|
||
# print("电机连接失败,退出。")
|
||
# exit(1)
|
||
#
|
||
# # 增加短暂延时,等待驱动器接口就绪
|
||
# time.sleep(1)
|
||
#
|
||
# print("初始化电机参数...")
|
||
# if not motor.init():
|
||
# print("电机初始化失败,退出。")
|
||
# motor.disconnect()
|
||
# exit(1)
|
||
#
|
||
# print("\n========== 电机位置控制测试 ==========")
|
||
# print("输入目标位置 (0~60000),输入 'q' 退出。函数已修改,只需输入开度。\n")
|
||
#
|
||
# try:
|
||
# while True:
|
||
# user_input = input("目标位置: ").strip()
|
||
# if user_input.lower() in ('q', 'quit', 'exit'):
|
||
# break
|
||
# if not user_input:
|
||
# continue
|
||
# try:
|
||
# pos = int(user_input)
|
||
# motor.set_position(pos)
|
||
# except ValueError:
|
||
# print("错误:请输入有效的整数。")
|
||
# except KeyboardInterrupt:
|
||
# print("\n用户中断测试。")
|
||
# finally:
|
||
# motor.disconnect()
|
||
# print("程序结束。")
|