变频器集成以及增加点动控制(0209)

This commit is contained in:
2026-02-09 11:36:37 +08:00
parent 88dfc53b9d
commit d6ad01274a
45 changed files with 7161 additions and 1578 deletions

View File

@ -1,88 +1,111 @@
# hardware/inverter.py
from pymodbus.exceptions import ModbusException
from math import e
import minimalmodbus
import time
from serial import SerialException
class InverterController:
def __init__(self, relay_controller):
self.relay_controller = relay_controller
self.max_frequency = 400.0 # 频率最大值
def __init__(self,port='/dev/ttyUSB0'):
self.inverter=None
self.port=port
# 变频器配置
self.config = {
'slave_id': 1,
'frequency_register': 0x01, # 2001H
'start_register': 0x00, # 2000H
'stop_register': 0x00, # 2000H用于停机
'start_command': 0x0013, # 正转点动运行
'stop_command': 0x0001 # 停机
}
def _connect(self)->bool:
"""连接变频器"""
try:
self.inverter = minimalmodbus.Instrument(self.port, 1)
# 2. 配置串口参数
self.inverter.serial.baudrate = 9600 # 波特率
self.inverter.serial.bytesize = 8 # 数据位
self.inverter.serial.parity = 'N' # 无校验
self.inverter.serial.stopbits = 1 # 停止位
self.inverter.serial.timeout = 1.0 # 超时时间
self.inverter.mode = minimalmodbus.MODE_RTU # RTU模式
return True
except SerialException as e:
print(f"串口占用或无法连接变频器串口:{e}")
return False
except Exception as e:
print(f"连接变频器异常:{e}")
return False
def set_frequency(self, frequency):
"""设置变频器频率"""
_ret=False
try:
if not self.relay_controller.modbus_client.connect():
print("无法连接网络继电器Modbus服务")
return False
# 使用最大频率变量计算百分比
percentage = frequency / self.max_frequency # 得到 0~1 的比例
value = int(percentage * 10000) # 转换为 -10000 ~ 10000 的整数
# 限制范围
value = max(-10000, min(10000, value))
result = self.relay_controller.modbus_client.write_register(
self.config['frequency_register'],
value,
slave=self.config['slave_id']
)
if isinstance(result, Exception):
print(f"设置频率失败: {result}")
return False
print(f"设置变频器频率为 {frequency}Hz")
return True
except ModbusException as e:
print(f"变频器Modbus通信错误: {e}")
return False
if(self._connect()):
frequency_value = int(frequency * 100)
self.inverter.write_register(0x7310, frequency_value)
_ret=True
else:
print(f'设置频率{frequency}失败')
except Exception as e:
print(f"设置频率{frequency}异常:{e}")
finally:
self.relay_controller.modbus_client.close()
if self.inverter:
self.inverter.serial.close()
return _ret
def control(self, action):
def read_frequency(self):
"""读取变频器频率"""
_ret=None
try:
if(self._connect()):
frequency_value = self.inverter.read_register(0x7310)
_ret=frequency_value / 100
else:
print(f"读取频率{frequency}失败")
except Exception as e:
print(f"读取频率{frequency}异常:{e}")
finally:
if self.inverter:
self.inverter.serial.close()
return _ret
def read_status(self):
"""读取变频器启动状态"""
_ret=None
try:
if(self._connect()):
status_value = self.inverter.read_register(0x3000)
_ret=status_value
else:
print(f"读取启动状态失败")
except Exception as e:
print(f"读取启动状态异常:{e}")
finally:
if self.inverter:
self.inverter.serial.close()
return _ret
def control(self, action,frequency=230):
"""控制变频器启停"""
# 先检查动作是否有效
if action not in ['start', 'stop']:
print(f"无效操作: {action}")
return False
_ret=False
# 先检查动作是否有
try:
if not self.relay_controller.modbus_client.connect():
print("无法连接网络继电器Modbus服务")
return False
if action == 'start':
result = self.relay_controller.modbus_client.write_register(
address=self.config['start_register'],
value=self.config['start_command'],
slave=self.config['slave_id']
)
print("启动变频器")
elif action == 'stop':
result = self.relay_controller.modbus_client.write_register(
address=self.config['start_register'],
value=self.config['stop_command'],
slave=self.config['slave_id']
)
print("停止变频器")
if isinstance(result, Exception):
print(f"控制失败: {result}")
return False
return True
except ModbusException as e:
if(self._connect()):
if action == 'start':
status_value = self.inverter.read_register(0x3000)
##读取3000H可直接读取变频器的当前状态0001正转运行0002反转运行0003停机0004电机参数辨识0005故障
if status_value==3:
frequency_value = int(frequency * 100)
self.inverter.write_register(0x7310, frequency_value)
time.sleep(1)
self.inverter.write_register(0x2000, 1) # 1=正转运行
print("启动变频器")
elif action == 'stop':
self.inverter.write_register(0x2000, 5) # 6=减速停机,5自由停机
print("停止变频器")
_ret=True
else:
print("连接变频器失败")
except Exception as e:
print(f"变频器控制错误: {e}")
return False
finally:
self.relay_controller.modbus_client.close()
if self.inverter:
self.inverter.serial.close()
return _ret

View File

@ -29,7 +29,7 @@ class RelayController:
self.host = host
self.port = port
self.modbus_client = ModbusTcpClient(host, port=port)
#遥1 DO 7 左 DO8 右 角度 摇2DO 15下 13上 12 往后 14往前 下料斗DO7开 D09关
#遥1 DO 7 左 DO8 右 角度 摇2DO 15下 13上 12 启动振捣 14停止振捣 下料斗DO7开 D09关
# 继电器命令原始Socket
self.relay_commands = {
self.RING: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'},
@ -299,6 +299,22 @@ class RelayController:
print(f"上料斗振动关闭完成,延迟{delay_seconds}")
self.control(self.BREAK_ARCH_UPPER, 'close')
def control_arch_lower_open_async(self,delay_seconds: float = 15):
"""异步控制上料斗振动
Args:
delay_seconds: 延迟关闭时间默认15秒
"""
# 关闭下料斗出砼门
self.control(self.BREAK_ARCH_LOWER, 'open')
# 异步5秒后关闭
threading.Thread(target=lambda d: self._close_break_arch_lower(delay_seconds),args=(delay_seconds,), daemon=True, name="_close_break_arch_lower").start()
def _close_break_arch_lower(self, delay_seconds: float = 15):
time.sleep(delay_seconds)
print(f"下料斗振动关闭完成,延迟{delay_seconds}")
self.control(self.BREAK_ARCH_LOWER, 'close')
def close_all(self):

View File

@ -1,184 +0,0 @@
# hardware/transmitter.py
from pymodbus.exceptions import ModbusException
import socket
from config.ini_manager import ini_manager
from config.settings import app_set_config
class TransmitterController:
def __init__(self, relay_controller):
self.relay_controller = relay_controller
# 变送器配置
self.config = {
1: { # 上料斗
'slave_id': 1,
'weight_register': 0x01,
'register_count': 2
},
2: { # 下料斗
'slave_id': 2,
'weight_register': 0x01,
'register_count': 2
}
}
# 备份 modbus 读取数据)
def read_data_bak(self, transmitter_id):
"""读取变送器数据"""
try:
if transmitter_id not in self.config:
print(f"无效变送器ID: {transmitter_id}")
return None
config = self.config[transmitter_id]
if not self.relay_controller.modbus_client.connect():
print("无法连接网络继电器Modbus服务")
return None
result = self.relay_controller.modbus_client.read_holding_registers(
address=config['weight_register'],
count=config['register_count'],
slave=config['slave_id']
)
if isinstance(result, Exception):
print(f"读取变送器 {transmitter_id} 失败: {result}")
return None
# 根据图片示例,正确解析数据
if config['register_count'] == 2:
# 获取原始字节数组
raw_data = result.registers
# 组合成32位整数
weight = (raw_data[0] << 16) + raw_data[1]
weight = weight / 1000.0 # 单位转换为千克
elif config['register_count'] == 1:
weight = float(result.registers[0])
else:
print(f"不支持的寄存器数量: {config['register_count']}")
return None
print(f"变送器 {transmitter_id} 读取重量: {weight}kg")
return weight
except ModbusException as e:
print(f"Modbus通信错误: {e}")
return None
except Exception as e:
print(f"数据解析错误: {e}")
return None
finally:
self.relay_controller.modbus_client.close()
# 直接读取 变送器返回的数据并解析
def read_data(self, transmitter_id):
"""
Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗
return: 读取成功返回重量 weight: int, 失败返回 None
"""
TIMEOUT = 2 # 超时时间为 2秒
BUFFER_SIZE= 1024
IP = None
PORT = None
weight = 0
if transmitter_id == 1:
# 上料斗变送器的信息:
IP = ini_manager.upper_transmitter_ip
PORT = ini_manager.upper_transmitter_port
elif transmitter_id == 2:
# 下料斗变送器的信息:
IP = ini_manager.lower_transmitter_ip
PORT = ini_manager.lower_transmitter_port
if not IP or not PORT:
print(f"未配置变送器 {transmitter_id} 的IP或PORT")
return 0
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.settimeout(TIMEOUT)
s.connect((IP, PORT))
# print(f"连接上料斗变送器 {IP}:{PORT} 成功")
# 接收数据变送器主动推送recv即可获取数据
data = s.recv(BUFFER_SIZE)
if data:
# print(f"收到原始数据:{data}")
# 提取出完整的一个数据包 (\r\n结尾)
packet = self.get_latest_valid_packet(data)
if not packet:
print("未获取到有效数据包!!")
return None
# 解析重量
weight = self.parse_weight(packet)
else:
print("未收到设备数据")
except ConnectionRefusedError:
print(f"变送器连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)")
except socket.timeout:
print(f"读取变送器数据超时:{TIMEOUT}秒内未收到数据")
except Exception as e:
print(f"读取异常:{e}")
# 成功返回重量int失败返回None
return weight
def get_latest_valid_packet(self, raw_data):
"""
解决TCP粘包:
从原始数据中,筛选所有有效包,返回最新的一个有效包
有效包标准: 1. 能UTF-8解码 2. 按逗号拆分≥3个字段 3. 第三个字段含数字(重量)
"""
DELIMITER = b'\r\n'
# 1. 按分隔符拆分,过滤空包
packets = [p for p in raw_data.split(DELIMITER) if p]
if not packets:
return None
valid_packets = []
for packet in packets:
try:
# 过滤无效ASCII字符只保留可见字符
valid_chars = [c for c in packet if 32 <= c <= 126]
filtered_packet = bytes(valid_chars)
# 2. 验证解码
data_str = filtered_packet.decode('utf-8').strip()
# 3. 验证字段数量
parts = data_str.split(',')
if len(parts) < 3:
continue
# 4. 验证重量字段含数字
weight_part = parts[2].strip()
if not any(char.isdigit() for char in weight_part):
continue
# 满足所有条件,加入有效包列表
valid_packets.append(packet)
except (UnicodeDecodeError, IndexError):
# 解码失败或字段异常,跳过该包
continue
# 返回最后一个有效包最新无有效包则返回None
return valid_packets[-1] if valid_packets else None
def parse_weight(self, packet_data):
"""解析重量函数:提取重量数值(如从 b'ST,NT,+0000175\r\n' 中提取 175)"""
try:
data_str = packet_data.decode('utf-8').strip()
parts = data_str.split(',')
# 确保有完整的数据包,三个字段
if len(parts) < 3:
print(f"parse_weight: 包格式错误(字段不足):{data_str}")
return None
weight_part = parts[2].strip()
return int(''.join(filter(str.isdigit, weight_part)))
except (IndexError, ValueError, UnicodeDecodeError) as e:
# print(f"数据解析失败:{e},原始数据包:{packet_data}")
return None

View File

@ -16,6 +16,10 @@ class TransmitterController:
self.is_start_lower=False
self.start_time_upper=None
self.start_time_lower=None
self.upper_ip=ini_manager.upper_transmitter_ip
self.upper_port=ini_manager.upper_transmitter_port
self.lower_ip=ini_manager.lower_transmitter_ip
self.lower_port=ini_manager.lower_transmitter_port
# 变送器配置
self.config = {
1: { # 上料斗
@ -93,12 +97,12 @@ class TransmitterController:
weight = None
if transmitter_id == 1:
# 上料斗变送器的信息:
IP = ini_manager.upper_transmitter_ip
PORT = ini_manager.upper_transmitter_port
IP = self.upper_ip
PORT = self.upper_port
elif transmitter_id == 2:
# 下料斗变送器的信息:
IP = ini_manager.lower_transmitter_ip
PORT = ini_manager.lower_transmitter_port
IP = self.lower_ip
PORT = self.lower_port
if not IP or not PORT:
print(f"未配置变送器 {transmitter_id} 的IP或PORT")

200
hardware/transmitter_bak.py Normal file
View File

@ -0,0 +1,200 @@
# hardware/transmitter.py
import socket
import threading
from config.ini_manager import ini_manager
from config.settings import app_set_config
import time
class TransmitterController:
def __init__(self):
self.upper_ip = ini_manager.upper_transmitter_ip
self.upper_port = ini_manager.upper_transmitter_port
self.lower_ip = ini_manager.lower_transmitter_ip
self.lower_port = ini_manager.lower_transmitter_port
# 存储最新重量值
self.latest_weights = {1: None, 2: None}
# 存储连接状态
self.connection_status = {1: False, 2: False}
# 线程控制
self.running = True
self.threads = {}
# 连接配置
self.TIMEOUT = 5 # 连接超时时间
self.BUFFER_SIZE = 1024
# 启动后台接收线程
self._start_receiver_threads()
def _start_receiver_threads(self):
"""启动后台接收线程"""
for transmitter_id in [1, 2]:
if (transmitter_id == 1 and self.upper_ip and self.upper_port) or \
(transmitter_id == 2 and self.lower_ip and self.lower_port):
thread = threading.Thread(
target=self._continuous_receiver,
args=(transmitter_id,),
daemon=True,
name=f'transmitter_receiver_{transmitter_id}'
)
thread.start()
self.threads[transmitter_id] = thread
print(f"启动变送器 {transmitter_id} 后台接收线程")
def _continuous_receiver(self, transmitter_id):
"""后台持续接收数据的线程函数"""
while self.running:
IP = None
PORT = None
if transmitter_id == 1:
IP = self.upper_ip
PORT = self.upper_port
elif transmitter_id == 2:
IP = self.lower_ip
PORT = self.lower_port
if not IP or not PORT:
time.sleep(5)
continue
sock = None
try:
# 创建连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.TIMEOUT)
sock.connect((IP, PORT))
self.connection_status[transmitter_id] = True
print(f"变送器 {transmitter_id} 连接成功: {IP}:{PORT}")
# 持续接收数据
while self.running:
try:
data = sock.recv(self.BUFFER_SIZE)
if data:
# 提取有效数据包
packet = self.get_latest_valid_packet(data)
if packet:
# 解析重量
weight = self.parse_weight(packet)
if weight is not None:
self.latest_weights[transmitter_id] = weight
# 可选:打印接收到的重量
# print(f"变送器 {transmitter_id} 重量: {weight}")
else:
# 连接关闭
print(f"变送器 {transmitter_id} 连接关闭")
break
except socket.timeout:
# 超时是正常的,继续接收
continue
except Exception as e:
print(f"接收数据异常: {e}")
break
except ConnectionRefusedError:
print(f"变送器 {transmitter_id} 连接失败:{IP}:{PORT} 拒绝连接")
except Exception as e:
print(f"变送器 {transmitter_id} 异常:{e}")
finally:
self.connection_status[transmitter_id] = False
if sock:
try:
sock.close()
except:
pass
# 重试间隔
time.sleep(3)
# 直接读取 变送器返回的数据(从缓存中获取)
def read_data_sub(self, transmitter_id):
"""
Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗
return: 读取成功返回重量 weight: int, 失败返回 None
"""
# 直接返回缓存的最新重量值
return self.latest_weights.get(transmitter_id)
def get_connection_status(self, transmitter_id):
"""
获取变送器连接状态
Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗
return: 连接状态 bool
"""
return self.connection_status.get(transmitter_id, False)
def stop(self):
"""停止后台线程"""
self.running = False
# 等待线程结束
for thread in self.threads.values():
if thread.is_alive():
thread.join(timeout=2)
print("变送器后台接收线程已停止")
def get_latest_valid_packet(self, raw_data):
"""
解决TCP粘包:
从原始数据中,筛选所有有效包,返回最新的一个有效包
有效包标准: 1. 能UTF-8解码 2. 按逗号拆分≥3个字段 3. 第三个字段含数字(重量)
"""
DELIMITER = b'\r\n'
# 1. 按分隔符拆分,过滤空包
packets = [p for p in raw_data.split(DELIMITER) if p]
if not packets:
return None
valid_packets = []
for packet in packets:
try:
# 过滤无效ASCII字符只保留可见字符
valid_chars = [c for c in packet if 32 <= c <= 126]
filtered_packet = bytes(valid_chars)
# 2. 验证解码
data_str = filtered_packet.decode('utf-8').strip()
# 3. 验证字段数量
parts = data_str.split(',')
if len(parts) < 3:
continue
# 4. 验证重量字段含数字
weight_part = parts[2].strip()
if not any(char.isdigit() for char in weight_part):
continue
# 满足所有条件,加入有效包列表
valid_packets.append(packet)
except (UnicodeDecodeError, IndexError):
# 解码失败或字段异常,跳过该包
continue
# 返回最后一个有效包最新无有效包则返回None
return valid_packets[-1] if valid_packets else None
def parse_weight(self, packet_data):
"""解析重量函数:提取重量数值(如从 b'ST,NT,+0000175\r\n' 中提取 175)"""
try:
data_str = packet_data.decode('utf-8').strip()
parts = data_str.split(',')
# 确保有完整的数据包,三个字段
if len(parts) < 3:
print(f"parse_weight: 包格式错误(字段不足):{data_str}")
return None
weight_part = parts[2].strip()
return int(''.join(filter(str.isdigit, weight_part)))
except (IndexError, ValueError, UnicodeDecodeError) as e:
# print(f"数据解析失败:{e},原始数据包:{packet_data}")
return None
def read_data(self,transmitter_id):
"""获取重量函数根据变送器ID获取当前重量,三次"""
max_try_times=5
try_times=0
while try_times<max_try_times:
weight=self.read_data_sub(transmitter_id)
if weight is not None:
return weight
try_times+=1
print(f'-----获取重量异常-------------- transmitter_id: {transmitter_id}')
print(f'-----获取重量异常-------------- transmitter_id: {transmitter_id}')
return None

View File

@ -371,6 +371,8 @@ class OmronFinsPollingService:
if __name__ == "__main__":
def on_data_update(data: int, binary: str):
#4即将振捣室5振捣室 64即将搅拌楼 66到达搅拌楼
#37振捣室,32在途中,96,98在搅拌楼
print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}")
def on_status_change(old_status: FinsServiceStatus, new_status: FinsServiceStatus, message: str):