stage_one
This commit is contained in:
@ -1,9 +1,12 @@
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
import binascii
|
||||
from typing import Optional, Callable, Dict, Any, Set
|
||||
from collections import Counter
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
from .crc16 import crc16
|
||||
from .command_hex import command_hex
|
||||
|
||||
@ -12,7 +15,8 @@ class rfid_service:
|
||||
"""
|
||||
RFID读写器服务
|
||||
"""
|
||||
def __init__(self, host='192.168.1.190', port=6000):
|
||||
# callback_signal=Signal(int,str)
|
||||
def __init__(self, host='192.168.250.67', port=6000):
|
||||
"""
|
||||
初始化RFID控制器
|
||||
|
||||
@ -34,9 +38,9 @@ class rfid_service:
|
||||
# 需要过滤掉的数据(字符串)
|
||||
self._filter_value = None
|
||||
|
||||
self.check_time_seconds = 60.0 # 采集数据时间(秒)
|
||||
self.check_time_seconds = 5.0 # 采集数据时间(秒)
|
||||
# 超时设置
|
||||
self._connect_timeout = 5.0 # 连接超时时间(秒)
|
||||
self._connect_timeout = 1.0 # 连接超时时间(秒)
|
||||
self.client_socket = None
|
||||
self.connected = False
|
||||
#链接失败次数
|
||||
@ -349,7 +353,7 @@ class rfid_service:
|
||||
#endregion
|
||||
|
||||
|
||||
def start_receiver(self, callback: Optional[Callable[[str], None]] = None)->bool:
|
||||
def start_receiver(self, callback: Optional[Callable[[int,str], None]] = None)->bool:
|
||||
"""
|
||||
开始接收RFID推送的数据
|
||||
|
||||
@ -439,14 +443,21 @@ class rfid_service:
|
||||
|
||||
if data:
|
||||
loc_str = command_hex.parse_user_data_hex(data)
|
||||
print(f"收到RFID推送数据: {binascii.hexlify(data).decode()}")
|
||||
raw_data = binascii.hexlify(data).decode()
|
||||
print(f"收到RFID推送数据: {raw_data}")
|
||||
|
||||
# 保存到文件
|
||||
with open('rfid_data.log', 'a') as f:
|
||||
timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3]
|
||||
f.write(f"[{timestamp}] 解析数据: {loc_str}, 原始数据: {raw_data}\n")
|
||||
|
||||
if loc_str:
|
||||
# 将数据添加到缓冲区
|
||||
with self._data_lock:
|
||||
if self._filter_value == loc_str:
|
||||
continue
|
||||
self._data_buffer.append(loc_str)
|
||||
|
||||
time.sleep(1)
|
||||
self._pause_receive = True
|
||||
self._process_collected_data()
|
||||
|
||||
@ -471,12 +482,15 @@ class rfid_service:
|
||||
most_common_string, count = counter.most_common(1)[0]
|
||||
print(f"出现次数最多的字符串是: '{most_common_string}', 出现次数: {count}")
|
||||
|
||||
# 使用出现次数最多的字符串作为结果传递给回调
|
||||
self._callback(most_common_string)
|
||||
# 使用出现次数最多的字符串作为结果传递给回调函数
|
||||
# self.callback_signal.emit(1,most_common_string)
|
||||
self._callback(1,most_common_string)
|
||||
else:
|
||||
# 空缓冲区情况
|
||||
print("数据缓冲区为空")
|
||||
self._callback(None)
|
||||
# 使用None作为结果传递给回调函数
|
||||
# self.callback_signal.emit(0,'')
|
||||
self._callback(0,'')
|
||||
|
||||
print(f"收集了{len(self._data_buffer)}条RFID数据,过滤后数据为{most_common_string}")
|
||||
except Exception as e:
|
||||
|
||||
@ -35,8 +35,8 @@ class RelayController:
|
||||
self.RING: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'},
|
||||
self.UPPER_TO_JBL: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'},
|
||||
self.UPPER_TO_ZD: {'open': '00000000000601050002FF00', 'close': '000000000006010500020000'},
|
||||
self.DOOR_LOWER_OPEN: {'open': '00000000000601050006FF00', 'close': '00000000000601050006FF00'},
|
||||
self.DOOR_LOWER_CLOSE: {'open': '00000000000601050008FF00', 'close': '000000000006010500080000'},
|
||||
self.DOOR_LOWER_OPEN: {'open': '00000000000601050006FF00', 'close': '000000000006010500060000'},
|
||||
self.DOOR_LOWER_CLOSE: {'open': '00000000000601050008FF00', 'close':'000000000006010500080000'},
|
||||
self.DOOR_UPPER_OPEN: {'open': '00000000000601050003FF00', 'close': '000000000006010500030000'},
|
||||
self.DOOR_UPPER_CLOSE: {'open': '00000000000601050004FF00', 'close': '000000000006010500040000'},
|
||||
self.BREAK_ARCH_UPPER: {'open': '0000000000060105000AFF00', 'close': '0000000000060105000A0000'},
|
||||
@ -58,11 +58,14 @@ class RelayController:
|
||||
self.BREAK_ARCH_UPPER: 3,
|
||||
self.BREAK_ARCH_LOWER: 4
|
||||
}
|
||||
|
||||
# 添加线程锁,保护对下料斗控制的并发访问
|
||||
self.door_control_lock = threading.Lock()
|
||||
|
||||
def send_command(self, command_hex):
|
||||
"""发送原始Socket命令"""
|
||||
if app_set_config.debug_mode:
|
||||
return None
|
||||
# if app_set_config.debug_mode:
|
||||
# return None
|
||||
|
||||
try:
|
||||
byte_data = binascii.unhexlify(command_hex)
|
||||
@ -102,32 +105,164 @@ class RelayController:
|
||||
def control_upper_close(self):
|
||||
"""控制上料斗关"""
|
||||
# 关闭上料斗出砼门
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'open')
|
||||
# 异步5秒后关闭
|
||||
threading.Thread(target=self._close_upper_5s, daemon=True,name="close_upper_5s").start()
|
||||
threading.Thread(target=self._close_upper_s, daemon=True,name="close_upper_s").start()
|
||||
|
||||
def control_upper_close_sync(self,duration=5):
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'open')
|
||||
time.sleep(duration)
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'close')
|
||||
|
||||
|
||||
def control_lower_close(self):
|
||||
"""控制下料斗关"""
|
||||
# 关闭下料斗出砼门
|
||||
self.control(self.DOOR_LOWER_CLOSE, 'open')
|
||||
thread_name = threading.current_thread().name
|
||||
print(f"[{thread_name}] 尝试控制下料斗关闭")
|
||||
|
||||
with self.door_control_lock:
|
||||
print(f"[{thread_name}] 获得下料斗控制锁,执行关闭操作")
|
||||
# 关闭下料斗出砼门
|
||||
self.control(self.DOOR_LOWER_OPEN, 'close')
|
||||
self.control(self.DOOR_LOWER_CLOSE, 'open')
|
||||
time.sleep(3)
|
||||
self.control(self.DOOR_LOWER_CLOSE, 'close')
|
||||
print(f"[{thread_name}] 下料斗关闭完成,释放控制锁")
|
||||
# 异步5秒后关闭
|
||||
threading.Thread(target=self._close_lower_5s, daemon=True,name="close_lower_5s").start()
|
||||
# threading.Thread(target=self._close_lower_5s, daemon=True,name="close_lower_5s").start()
|
||||
|
||||
def control_upper_open_sync(self,duration):
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'close')
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(duration)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
|
||||
def control_upper_close_sync(self,duration):
|
||||
thread_name = threading.current_thread().name
|
||||
print(f"[{thread_name}] 尝试执行上料斗同步关闭,实际操作下料斗")
|
||||
|
||||
with self.door_control_lock:
|
||||
print(f"[{thread_name}] 获得下料斗控制锁,执行同步关闭操作")
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'open')
|
||||
time.sleep(duration)
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'close')
|
||||
print(f"[{thread_name}] 同步关闭操作完成,释放控制锁")
|
||||
|
||||
def control_upper_open(self):
|
||||
#关闭信号才能生效
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'close')
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.2)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.2)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
#保持8秒
|
||||
time.sleep(8)
|
||||
#8秒后再开5秒
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
time.sleep(0.1)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'open')
|
||||
time.sleep(0.5)
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
|
||||
def control_ring_open(self):
|
||||
"""控制下料斗关"""
|
||||
# 关闭下料斗出砼门
|
||||
self.control(self.RING, 'open')
|
||||
# 异步5秒后关闭
|
||||
threading.Thread(target=self._close_ring, daemon=True,name="close_ring").start()
|
||||
threading.Thread(target=self._close_ring, daemon=True,name="_close_ring").start()
|
||||
|
||||
def _close_upper_5s(self):
|
||||
time.sleep(5)
|
||||
def _close_upper_s(self):
|
||||
time.sleep(16)
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'close')
|
||||
print("上料斗关闭完成")
|
||||
|
||||
def _close_lower_5s(self):
|
||||
time.sleep(5)
|
||||
time.sleep(6)
|
||||
self.control(self.DOOR_LOWER_CLOSE, 'close')
|
||||
|
||||
def _close_ring(self):
|
||||
time.sleep(3)
|
||||
self.control(self.RING, 'close')
|
||||
|
||||
def control_arch_lower_open(self):
|
||||
"""控制下料斗关"""
|
||||
# 关闭下料斗出砼门
|
||||
self.control(self.BREAK_ARCH_LOWER, 'open')
|
||||
# 异步5秒后关闭
|
||||
threading.Thread(target=self._close_break_arch_lower, daemon=True,name="_close_break_arch_lower").start()
|
||||
def _close_break_arch_lower(self):
|
||||
time.sleep(3)
|
||||
self.control(self.BREAK_ARCH_LOWER, 'close')
|
||||
|
||||
|
||||
def control_arch_upper_open(self):
|
||||
"""控制上料斗关"""
|
||||
# 关闭下料斗出砼门
|
||||
self.control(self.BREAK_ARCH_UPPER, 'open')
|
||||
# 异步5秒后关闭
|
||||
threading.Thread(target=self._close_break_arch_upper, daemon=True,name="_close_break_arch_upper").start()
|
||||
def _close_break_arch_upper(self):
|
||||
time.sleep(3)
|
||||
self.control(self.BREAK_ARCH_UPPER, 'close')
|
||||
|
||||
|
||||
|
||||
def close_all(self):
|
||||
"""关闭所有继电器"""
|
||||
self.control(self.UPPER_TO_JBL, 'close')
|
||||
self.control(self.UPPER_TO_ZD, 'close')
|
||||
self.control(self.BREAK_ARCH_UPPER, 'close')
|
||||
self.control(self.BREAK_ARCH_LOWER, 'close')
|
||||
self.control(self.RING, 'close')
|
||||
self.control(self.DOOR_LOWER_OPEN, 'close')
|
||||
self.control(self.DOOR_LOWER_CLOSE, 'close')
|
||||
self.control(self.DOOR_UPPER_OPEN, 'close')
|
||||
self.control(self.DOOR_UPPER_CLOSE, 'close')
|
||||
|
||||
184
hardware/transmitter copy.py
Normal file
184
hardware/transmitter copy.py
Normal file
@ -0,0 +1,184 @@
|
||||
# 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
|
||||
|
||||
@ -3,12 +3,19 @@ from pymodbus.exceptions import ModbusException
|
||||
import socket
|
||||
from config.ini_manager import ini_manager
|
||||
from config.settings import app_set_config
|
||||
import time
|
||||
|
||||
|
||||
|
||||
class TransmitterController:
|
||||
def __init__(self, relay_controller):
|
||||
self.relay_controller = relay_controller
|
||||
self.test_upper_weight=5043
|
||||
self.test_lower_weight=1256
|
||||
self.is_start_upper=False
|
||||
self.is_start_lower=False
|
||||
self.start_time_upper=None
|
||||
self.start_time_lower=None
|
||||
# 变送器配置
|
||||
self.config = {
|
||||
1: { # 上料斗
|
||||
@ -73,7 +80,8 @@ class TransmitterController:
|
||||
self.relay_controller.modbus_client.close()
|
||||
|
||||
# 直接读取 变送器返回的数据并解析
|
||||
def read_data(self, transmitter_id):
|
||||
def read_data_normal(self, transmitter_id):
|
||||
|
||||
"""
|
||||
Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗
|
||||
return: 读取成功返回重量 weight: int, 失败返回 None
|
||||
@ -82,9 +90,7 @@ class TransmitterController:
|
||||
BUFFER_SIZE= 1024
|
||||
IP = None
|
||||
PORT = None
|
||||
weight = 0
|
||||
|
||||
|
||||
weight = 0
|
||||
if transmitter_id == 1:
|
||||
# 上料斗变送器的信息:
|
||||
IP = ini_manager.upper_transmitter_ip
|
||||
@ -97,8 +103,86 @@ class TransmitterController:
|
||||
if not IP or not PORT:
|
||||
print(f"未配置变送器 {transmitter_id} 的IP或PORT")
|
||||
return 0
|
||||
if app_set_config.debug_mode:
|
||||
print(f"调试模式,未读数据({transmitter_id},IP: {IP}:{PORT})")
|
||||
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 read_data(self, transmitter_id):
|
||||
|
||||
"""
|
||||
测试用:模拟读取变送器数据mock
|
||||
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
|
||||
|
||||
if self.is_start_upper:
|
||||
if self.start_time_upper is None:
|
||||
self.start_time_upper=time.time()
|
||||
weight=self.test_upper_weight-50*(time.time()-self.start_time_upper)
|
||||
if weight<0:
|
||||
weight=0
|
||||
else:
|
||||
weight=self.test_upper_weight
|
||||
self.start_time_upper=None
|
||||
|
||||
return weight
|
||||
|
||||
elif transmitter_id == 2:
|
||||
# 下料斗变送器的信息:
|
||||
IP = ini_manager.lower_transmitter_ip
|
||||
PORT = ini_manager.lower_transmitter_port
|
||||
|
||||
if self.is_start_lower:
|
||||
if self.start_time_lower is None:
|
||||
self.start_time_lower=time.time()
|
||||
weight=self.test_lower_weight-50*(time.time()-self.start_time_lower)
|
||||
else:
|
||||
weight=self.test_lower_weight
|
||||
self.start_time_lower=None
|
||||
|
||||
return weight
|
||||
|
||||
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)
|
||||
|
||||
405
hardware/upper_plc.py
Normal file
405
hardware/upper_plc.py
Normal file
@ -0,0 +1,405 @@
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import threading
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import Optional, Callable
|
||||
|
||||
class FinsServiceStatus(Enum):
|
||||
"""服务状态枚举"""
|
||||
DISCONNECTED = "未连接"
|
||||
CONNECTING = "连接中"
|
||||
CONNECTED = "已连接"
|
||||
POLLING = "轮询中"
|
||||
ERROR = "错误"
|
||||
STOPPED = "已停止"
|
||||
|
||||
class FinsPoolFullError(Exception):
|
||||
"""连接池已满异常"""
|
||||
pass
|
||||
|
||||
class OmronFinsPollingService:
|
||||
"""欧姆龙FINS协议数据轮询服务(严格三指令版本)"""
|
||||
|
||||
def __init__(self, plc_ip: str, plc_port: int = 9600):
|
||||
"""
|
||||
初始化FINS服务
|
||||
|
||||
Args:
|
||||
plc_ip: PLC IP地址
|
||||
plc_port: PLC端口,默认9600
|
||||
"""
|
||||
self.plc_ip = plc_ip
|
||||
self.plc_port = plc_port
|
||||
|
||||
# 服务状态
|
||||
self._status = FinsServiceStatus.DISCONNECTED
|
||||
self._socket: Optional[socket.socket] = None
|
||||
|
||||
# 轮询控制
|
||||
self._polling_thread: Optional[threading.Thread] = None
|
||||
self._stop_event = threading.Event()
|
||||
self._polling_interval = 1.0
|
||||
|
||||
# 回调函数
|
||||
self._data_callbacks = []
|
||||
self._status_callbacks = []
|
||||
|
||||
# 最新数据
|
||||
self._latest_data: Optional[int] = None
|
||||
self._last_update_time: Optional[float] = None
|
||||
|
||||
# 配置日志
|
||||
self._setup_logging()
|
||||
|
||||
def _setup_logging(self):
|
||||
"""配置日志"""
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
self.logger = logging.getLogger("FinsPollingService")
|
||||
|
||||
def _update_status(self, new_status: FinsServiceStatus, message: str = ""):
|
||||
"""更新状态并触发回调"""
|
||||
old_status = self._status
|
||||
if old_status != new_status:
|
||||
self._status = new_status
|
||||
self.logger.info(f"状态变更: {old_status.value} -> {new_status.value} {message}")
|
||||
|
||||
for callback in self._status_callbacks:
|
||||
try:
|
||||
callback(old_status, new_status, message)
|
||||
except Exception as e:
|
||||
self.logger.error(f"状态回调执行失败: {e}")
|
||||
|
||||
def _send_and_receive(self, data: bytes, expected_length: int = 1024) -> bytes:
|
||||
"""发送数据并接收响应"""
|
||||
if not self._socket:
|
||||
raise ConnectionError("Socket未连接")
|
||||
|
||||
# print("_send_and_receive发送:",data)
|
||||
self._socket.send(data)
|
||||
response = self._socket.recv(expected_length)
|
||||
return response
|
||||
|
||||
def _check_handshake_response(self, response: bytes):
|
||||
"""检查握手响应"""
|
||||
if len(response) < 24:
|
||||
raise ConnectionError("握手响应数据不完整")
|
||||
|
||||
# 检查响应头
|
||||
if response[0:4] != b'FINS':
|
||||
raise ConnectionError("无效的FINS响应头")
|
||||
|
||||
# 检查命令代码
|
||||
command_code = struct.unpack('>I', response[8:12])[0]
|
||||
if command_code != 0x01:
|
||||
raise ConnectionError(f"握手命令代码错误: 0x{command_code:08X}")
|
||||
|
||||
# 检查错误代码
|
||||
error_code = struct.unpack('>I', response[12:16])[0]
|
||||
if error_code == 0x20:
|
||||
raise FinsPoolFullError("FINS连接池已满")
|
||||
elif error_code != 0x00:
|
||||
raise ConnectionError(f"握手错误代码: 0x{error_code:08X}")
|
||||
|
||||
self.logger.info("握手成功")
|
||||
|
||||
def _check_query_response(self, response: bytes) -> int:
|
||||
"""检查查询响应并返回数据"""
|
||||
if len(response) < 30:
|
||||
raise ConnectionError("查询响应数据不完整")
|
||||
|
||||
# 检查响应头
|
||||
if response[0:4] != b'FINS':
|
||||
raise ConnectionError("无效的FINS响应头")
|
||||
|
||||
# 检查命令代码
|
||||
command_code = struct.unpack('>I', response[8:12])[0]
|
||||
if command_code != 0x02:
|
||||
raise ConnectionError(f"查询命令代码错误: 0x{command_code:08X}")
|
||||
|
||||
# 检查错误代码
|
||||
error_code = struct.unpack('>I', response[12:16])[0]
|
||||
if error_code != 0x00:
|
||||
raise ConnectionError(f"查询错误代码: 0x{error_code:08X}")
|
||||
|
||||
# 提取数据字节(最后一个字节)
|
||||
data_byte = response[-1]
|
||||
return data_byte
|
||||
|
||||
def _check_logout_response(self, response: bytes):
|
||||
"""检查注销响应"""
|
||||
if len(response) < 16:
|
||||
raise ConnectionError("注销响应数据不完整")
|
||||
|
||||
# 检查响应头
|
||||
if response[0:4] != b'FINS':
|
||||
raise ConnectionError("无效的FINS响应头")
|
||||
|
||||
# 检查命令代码
|
||||
command_code = struct.unpack('>I', response[8:12])[0]
|
||||
if command_code != 0x03:
|
||||
raise ConnectionError(f"注销命令代码错误: 0x{command_code:08X}")
|
||||
|
||||
# 检查错误代码
|
||||
error_code = struct.unpack('>I', response[12:16])[0]
|
||||
if error_code != 0x02:
|
||||
raise ConnectionError(f"注销错误代码: 0x{error_code:08X}")
|
||||
|
||||
self.logger.info("注销成功")
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""
|
||||
连接到PLC并完成握手
|
||||
|
||||
Returns:
|
||||
bool: 连接是否成功
|
||||
"""
|
||||
if self._status == FinsServiceStatus.CONNECTED:
|
||||
self.logger.warning("已经连接到PLC")
|
||||
return True
|
||||
|
||||
self._update_status(FinsServiceStatus.CONNECTING, "开始连接PLC")
|
||||
|
||||
try:
|
||||
# 创建socket连接
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.settimeout(10.0)
|
||||
self._socket.connect((self.plc_ip, self.plc_port))
|
||||
self.logger.info(f"TCP连接已建立: {self.plc_ip}:{self.plc_port}")
|
||||
|
||||
# 指令1: 握手
|
||||
# 46 49 4E 53 00 00 00 0C 00 00 00 00 00 00 00 00 00 00 00 DC
|
||||
handshake_cmd = bytes.fromhex("46 49 4E 53 00 00 00 0C 00 00 00 00 00 00 00 00 00 00 00 DC")
|
||||
self.logger.debug("发送握手指令")
|
||||
response = self._send_and_receive(handshake_cmd, 24)
|
||||
|
||||
# 检查握手响应
|
||||
self._check_handshake_response(response)
|
||||
|
||||
self._update_status(FinsServiceStatus.CONNECTED, "握手成功")
|
||||
return True
|
||||
|
||||
except FinsPoolFullError:
|
||||
self._update_status(FinsServiceStatus.ERROR, "连接池已满")
|
||||
raise
|
||||
except Exception as e:
|
||||
self._update_status(FinsServiceStatus.ERROR, f"连接失败: {e}")
|
||||
if self._socket:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
raise
|
||||
|
||||
def query_data(self) -> Optional[int]:
|
||||
"""
|
||||
查询PLC数据
|
||||
|
||||
Returns:
|
||||
int: 数据值(0-255)
|
||||
"""
|
||||
|
||||
if self._status != FinsServiceStatus.POLLING:
|
||||
raise ConnectionError("未连接到PLC,无法查询")
|
||||
|
||||
try:
|
||||
# 指令2: 查询
|
||||
# 46 49 4E 53 00 00 00 1A 00 00 00 02 00 00 00 00 80 00 30 00 E9 00 00 DC 00 00 01 01 B0 00 00 00 00 01
|
||||
query_cmd = bytes.fromhex("46 49 4E 53 00 00 00 1A 00 00 00 02 00 00 00 00 80 00 30 00 E9 00 00 DC 00 00 01 01 B0 00 00 00 00 01")
|
||||
self.logger.debug("发送查询指令")
|
||||
response = self._send_and_receive(query_cmd, 1024)
|
||||
|
||||
# 检查查询响应并提取数据
|
||||
data_byte = self._check_query_response(response)
|
||||
|
||||
self._latest_data = data_byte
|
||||
self._last_update_time = time.time()
|
||||
|
||||
# 触发数据回调
|
||||
binary_str = bin(data_byte)
|
||||
for callback in self._data_callbacks:
|
||||
try:
|
||||
callback(data_byte, binary_str)
|
||||
except Exception as e:
|
||||
self.logger.error(f"数据回调执行失败: {e}")
|
||||
|
||||
self.logger.debug(f"查询成功: 数据=0x{data_byte:02X} ({binary_str})")
|
||||
return data_byte
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"查询失败: {e}")
|
||||
raise
|
||||
|
||||
def disconnect(self):
|
||||
"""断开连接"""
|
||||
if self._socket:
|
||||
try:
|
||||
# 指令3: 注销
|
||||
# 46 49 4E 53 00 00 00 10 00 00 00 02 00 00 00 00 DC E9
|
||||
logout_cmd = bytes.fromhex("46 49 4E 53 00 00 00 10 00 00 00 02 00 00 00 00 00 00 00 DC 00 00 00 E9")
|
||||
self.logger.debug("发送注销指令")
|
||||
response = self._send_and_receive(logout_cmd, 24)
|
||||
|
||||
# 检查注销响应
|
||||
self._check_logout_response(response)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"注销过程中出错: {e}")
|
||||
finally:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
|
||||
self._update_status(FinsServiceStatus.DISCONNECTED, "连接已关闭")
|
||||
|
||||
def _polling_loop(self):
|
||||
"""轮询循环"""
|
||||
self.logger.info("数据轮询循环启动")
|
||||
|
||||
while not self._stop_event.is_set():
|
||||
try:
|
||||
if self._status == FinsServiceStatus.CONNECTED:
|
||||
self._update_status(FinsServiceStatus.POLLING, "正在查询数据")
|
||||
self.query_data()
|
||||
self._update_status(FinsServiceStatus.CONNECTED, "查询完成")
|
||||
else:
|
||||
# 尝试重新连接
|
||||
try:
|
||||
self.connect()
|
||||
except FinsPoolFullError:
|
||||
self.logger.error("连接池已满,等待后重试...")
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
self.logger.error(f"连接失败: {e}, 等待后重试...")
|
||||
time.sleep(2)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"轮询查询失败: {e}")
|
||||
self._update_status(FinsServiceStatus.ERROR, f"查询错误: {e}")
|
||||
# 查询失败不影响连接状态,保持CONNECTED状态
|
||||
self._update_status(FinsServiceStatus.CONNECTED, "准备下一次查询")
|
||||
time.sleep(1)
|
||||
|
||||
# 等待轮询间隔
|
||||
self._stop_event.wait(self._polling_interval)
|
||||
|
||||
self.logger.info("数据轮询循环停止")
|
||||
|
||||
def start_polling(self, interval: float = 1.0):
|
||||
"""
|
||||
启动数据轮询服务
|
||||
|
||||
Args:
|
||||
interval: 轮询间隔(秒)
|
||||
"""
|
||||
if self._polling_thread and self._polling_thread.is_alive():
|
||||
self.logger.warning("轮询服务已在运行")
|
||||
return
|
||||
|
||||
self._polling_interval = interval
|
||||
self._stop_event.clear()
|
||||
|
||||
# 先建立连接
|
||||
try:
|
||||
self.connect()
|
||||
except Exception as e:
|
||||
self.logger.error(f"初始连接失败: {e}")
|
||||
# 继续启动轮询,轮询循环会尝试重连
|
||||
|
||||
# 启动轮询线程
|
||||
self._polling_thread = threading.Thread(target=self._polling_loop, daemon=True)
|
||||
self._polling_thread.start()
|
||||
self.logger.info(f"数据轮询服务已启动,间隔: {interval}秒")
|
||||
|
||||
def stop_polling(self):
|
||||
"""停止数据轮询服务"""
|
||||
self.logger.info("正在停止数据轮询服务...")
|
||||
self._stop_event.set()
|
||||
|
||||
if self._polling_thread and self._polling_thread.is_alive():
|
||||
self._polling_thread.join(timeout=5.0)
|
||||
|
||||
self.disconnect()
|
||||
self._update_status(FinsServiceStatus.STOPPED, "服务已停止")
|
||||
self.logger.info("数据轮询服务已停止")
|
||||
|
||||
# === 公共接口 ===
|
||||
|
||||
def get_service_status(self) -> dict:
|
||||
"""获取服务状态"""
|
||||
return {
|
||||
'status': self._status.value,
|
||||
'is_connected': self._status == FinsServiceStatus.CONNECTED,
|
||||
'is_polling': self._polling_thread and self._polling_thread.is_alive(),
|
||||
'latest_data': self._latest_data,
|
||||
'latest_data_binary': bin(self._latest_data) if self._latest_data is not None else None,
|
||||
'last_update_time': self._last_update_time,
|
||||
'plc_ip': self.plc_ip,
|
||||
'plc_port': self.plc_port,
|
||||
'polling_interval': self._polling_interval
|
||||
}
|
||||
|
||||
def get_latest_data(self) -> Optional[int]:
|
||||
"""获取最新数据"""
|
||||
return self._latest_data
|
||||
|
||||
def get_latest_data_binary(self) -> Optional[str]:
|
||||
"""获取最新数据的二进制表示"""
|
||||
return bin(self._latest_data) if self._latest_data is not None else None
|
||||
|
||||
# === 回调注册接口 ===
|
||||
|
||||
def register_data_callback(self, callback: Callable[[int, str], None]):
|
||||
"""注册数据更新回调"""
|
||||
self._data_callbacks.append(callback)
|
||||
|
||||
def register_status_callback(self, callback: Callable[[FinsServiceStatus, FinsServiceStatus, str], None]):
|
||||
"""注册状态变化回调"""
|
||||
self._status_callbacks.append(callback)
|
||||
|
||||
def __enter__(self):
|
||||
"""上下文管理器入口"""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""上下文管理器出口,确保资源释放"""
|
||||
self.stop_polling()
|
||||
|
||||
|
||||
# 使用示例和测试
|
||||
if __name__ == "__main__":
|
||||
def on_data_update(data: int, binary: str):
|
||||
#4即将振捣室5振捣室 64即将搅拌楼 66到达搅拌楼
|
||||
print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}")
|
||||
|
||||
def on_status_change(old_status: FinsServiceStatus, new_status: FinsServiceStatus, message: str):
|
||||
print(f"[状态回调] {old_status.value} -> {new_status.value} : {message}")
|
||||
|
||||
# 创建服务实例
|
||||
service = OmronFinsPollingService("192.168.250.233") # 替换为实际PLC IP
|
||||
|
||||
# 注册回调
|
||||
service.register_data_callback(on_data_update)
|
||||
service.register_status_callback(on_status_change)
|
||||
|
||||
print("欧姆龙FINS数据轮询服务")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# 启动轮询服务,每2秒查询一次
|
||||
service.start_polling(interval=2.0)
|
||||
|
||||
# 主循环,定期显示服务状态
|
||||
counter = 0
|
||||
while True:
|
||||
status = service.get_service_status()
|
||||
counter += 1
|
||||
time.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n接收到Ctrl+C,正在停止服务...")
|
||||
finally:
|
||||
# 确保服务正确停止
|
||||
service.stop_polling()
|
||||
print("服务已安全停止")
|
||||
Reference in New Issue
Block a user