2026-01-08 18:09:22 +08:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
'''
|
2026-02-06 15:16:17 +08:00
|
|
|
|
# @Time : 2025/12/12 14:39
|
2026-01-08 18:09:22 +08:00
|
|
|
|
# @Author : reenrr
|
2026-02-06 15:16:17 +08:00
|
|
|
|
# @File : EMV.py
|
|
|
|
|
|
# @Desc : 网络继电器控制输入、输出设备 修改了接口,需测试
|
2026-01-08 18:09:22 +08:00
|
|
|
|
'''
|
|
|
|
|
|
import socket
|
|
|
|
|
|
import binascii
|
|
|
|
|
|
import time
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
from typing import Union
|
|
|
|
|
|
|
2026-01-08 18:09:22 +08:00
|
|
|
|
# 网络继电器的 IP 和端口
|
|
|
|
|
|
HOST = '192.168.5.18'
|
|
|
|
|
|
PORT = 50000
|
|
|
|
|
|
|
|
|
|
|
|
# 控件命名映射
|
2026-02-06 15:16:17 +08:00
|
|
|
|
SOLENOID_VALVE1 = 'solenoid_valve1' # 控制排料机构NG时的电磁阀1
|
|
|
|
|
|
SOLENOID_VALVE2 = 'solenoid_valve2' # 控制排料机构NG时的电磁阀2
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE1 = 'absorb_solenoid_valve1' # 控制吸取设备的电磁阀1
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE2 = 'absorb_solenoid_valve2' # 控制吸取设备的电磁阀2
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE3 = 'absorb_solenoid_valve3' # 控制吸取设备的电磁阀3
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE4 = 'absorb_solenoid_valve4' # 控制吸取设备的电磁阀4
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE5 = 'absorb_solenoid_valve5' # 控制吸取设备的电磁阀5
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
|
|
|
|
|
# 传感器命名映射
|
2026-02-06 15:16:17 +08:00
|
|
|
|
CONVEYOR1_SENSOR = 'conveyor1_sensor' # 传送带1的行程开关
|
|
|
|
|
|
CONVEYOR2_SENSOR = 'conveyor2_sensor' # 传送带2的行程开关
|
|
|
|
|
|
PRESS_SENSOR1 = 'press_sensor1' # 传送带1旁边的按压开关1
|
|
|
|
|
|
PRESS_SENSOR2 = 'press_sensor2' # 传送带1旁边的按压开关2
|
|
|
|
|
|
FIBER_SENSOR = 'fiber_sensor' # 传送带1旁边的光纤传感器
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
|
|
|
|
|
# 控件控制报文
|
|
|
|
|
|
valve_commands = {
|
|
|
|
|
|
SOLENOID_VALVE1: {
|
|
|
|
|
|
'open': '00000000000601050000FF00',
|
|
|
|
|
|
'close': '000000000006010500000000',
|
|
|
|
|
|
},
|
|
|
|
|
|
SOLENOID_VALVE2: {
|
|
|
|
|
|
'open': '00000000000601050001FF00',
|
|
|
|
|
|
'close': '000000000006010500010000',
|
|
|
|
|
|
},
|
2026-01-29 18:30:22 +08:00
|
|
|
|
ABSORB_SOLENOID_VALVE1: {
|
2026-01-08 18:09:22 +08:00
|
|
|
|
'open': '00000000000601050002FF00',
|
|
|
|
|
|
'close': '000000000006010500020000',
|
2026-01-29 18:30:22 +08:00
|
|
|
|
},
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE2: {
|
2026-02-06 15:16:17 +08:00
|
|
|
|
'open': '00000000000601050003FF00',
|
2026-01-29 18:30:22 +08:00
|
|
|
|
'close': '000000000006010500030000',
|
|
|
|
|
|
},
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE3: {
|
2026-02-06 15:16:17 +08:00
|
|
|
|
'open': '00000000000601050004FF00',
|
2026-01-29 18:30:22 +08:00
|
|
|
|
'close': '000000000006010500040000',
|
|
|
|
|
|
},
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE4: {
|
2026-02-06 15:16:17 +08:00
|
|
|
|
'open': '00000000000601050005FF00',
|
2026-01-29 18:30:22 +08:00
|
|
|
|
'close': '000000000006010500050000',
|
|
|
|
|
|
},
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE5: {
|
2026-02-06 15:16:17 +08:00
|
|
|
|
'open': '00000000000601050006FF00',
|
2026-01-29 18:30:22 +08:00
|
|
|
|
'close': '000000000006010500060000',
|
2026-01-08 18:09:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 读取状态命令
|
|
|
|
|
|
read_status_command = {
|
|
|
|
|
|
'devices': '000000000006010100000008',
|
|
|
|
|
|
'sensors': '000000000006010200000008'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 控件对应 DO 位(从低到高)
|
|
|
|
|
|
device_bit_map = {
|
|
|
|
|
|
SOLENOID_VALVE1: 0,
|
|
|
|
|
|
SOLENOID_VALVE2: 1,
|
2026-01-29 18:30:22 +08:00
|
|
|
|
ABSORB_SOLENOID_VALVE1: 2,
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE2: 3,
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE3: 4,
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE4: 5,
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE5: 6
|
2026-01-08 18:09:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
device_name_map = {
|
|
|
|
|
|
SOLENOID_VALVE1: "电磁阀1",
|
|
|
|
|
|
SOLENOID_VALVE2: "电磁阀2",
|
2026-01-29 18:30:22 +08:00
|
|
|
|
ABSORB_SOLENOID_VALVE1: "吸取装置电磁阀1",
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE2: "吸取装置电磁阀2",
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE3: "吸取装置电磁阀3",
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE4: "吸取装置电磁阀4",
|
|
|
|
|
|
ABSORB_SOLENOID_VALVE5: "吸取装置电磁阀5",
|
2026-01-08 18:09:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 传感器对应位(从低到高)
|
|
|
|
|
|
sensor_bit_map = {
|
2026-01-29 18:30:22 +08:00
|
|
|
|
FIBER_SENSOR: 0,
|
|
|
|
|
|
PRESS_SENSOR1: 1,
|
|
|
|
|
|
PRESS_SENSOR2: 2,
|
|
|
|
|
|
CONVEYOR1_SENSOR: 4,
|
|
|
|
|
|
CONVEYOR2_SENSOR: 3,
|
2026-01-08 18:09:22 +08:00
|
|
|
|
# 根据你继电器的配置,继续添加更多传感器
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sensor_name_map = {
|
2026-01-29 18:30:22 +08:00
|
|
|
|
FIBER_SENSOR: '光纤传感器',
|
2026-01-08 18:09:22 +08:00
|
|
|
|
PRESS_SENSOR1: '按压开关1',
|
|
|
|
|
|
PRESS_SENSOR2: '按压开关2',
|
2026-01-29 18:30:22 +08:00
|
|
|
|
CONVEYOR1_SENSOR: '传送带1开关',
|
|
|
|
|
|
CONVEYOR2_SENSOR: '传送带2开关'
|
2026-01-08 18:09:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 16:52:10 +08:00
|
|
|
|
|
2026-01-08 18:09:22 +08:00
|
|
|
|
class RelayController:
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
"""初始化继电器控制器"""
|
2026-02-06 15:16:17 +08:00
|
|
|
|
self.sock = None
|
|
|
|
|
|
self.is_connected = False # 长连接状态标记
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-02-26 15:42:22 +08:00
|
|
|
|
# 初始化时自动尝试建立长连接
|
|
|
|
|
|
print(f"正在初始化网络继电器并建立长连接")
|
|
|
|
|
|
connect_success = self.connect()
|
|
|
|
|
|
|
|
|
|
|
|
if connect_success:
|
|
|
|
|
|
print(f"网络继电器长连接建立成功:{HOST}:{PORT}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"网络继电器长连接建立失败")
|
|
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
def connect(self) -> bool:
|
|
|
|
|
|
"""
|
|
|
|
|
|
建立长连接
|
|
|
|
|
|
:return: True-连接成功,False-连接失败
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.is_connected:
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
|
self.sock.settimeout(3)
|
|
|
|
|
|
self.sock.connect((HOST, PORT))
|
|
|
|
|
|
self.is_connected = True
|
|
|
|
|
|
print(f"长连接建立成功:{HOST}:{PORT}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"长连接建立失败:{e}")
|
|
|
|
|
|
self.sock = None
|
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def disconnect(self):
|
|
|
|
|
|
"""断开长连接"""
|
|
|
|
|
|
if self.sock and self.is_connected:
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.sock.shutdown(socket.SHUT_RDWR)
|
|
|
|
|
|
self.sock.close()
|
|
|
|
|
|
print("长连接已正常断开")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"断开长连接时出现异常:{e}")
|
|
|
|
|
|
finally:
|
|
|
|
|
|
self.sock = None
|
|
|
|
|
|
self.is_connected = False
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
def send_command(self, command: str) -> Union[bytes, bool]:
|
2026-01-08 18:09:22 +08:00
|
|
|
|
"""
|
|
|
|
|
|
将十六进制字符串转换为字节数据并发送
|
|
|
|
|
|
:param command: 十六进制字符串
|
|
|
|
|
|
:return: 响应字节数据 / False
|
|
|
|
|
|
"""
|
2026-02-06 15:16:17 +08:00
|
|
|
|
if not self.is_connected or not self.sock:
|
|
|
|
|
|
print("长连接未建立,无法发送指令")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2026-01-08 18:09:22 +08:00
|
|
|
|
try:
|
|
|
|
|
|
byte_data = binascii.unhexlify(command)
|
2026-02-06 15:16:17 +08:00
|
|
|
|
self.sock.sendall(byte_data) # sendall确保数据全部发送
|
|
|
|
|
|
# 接收响应
|
|
|
|
|
|
response = self.sock.recv(1024)
|
|
|
|
|
|
if not response:
|
|
|
|
|
|
logging.warning("长连接接收空响应,可能连接已断开")
|
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
|
return False
|
|
|
|
|
|
# print(f"收到响应: {binascii.hexlify(response)}")
|
|
|
|
|
|
# 校验响应
|
|
|
|
|
|
return response
|
|
|
|
|
|
except socket.timeout:
|
|
|
|
|
|
print("长连接发送/接收超时")
|
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
|
return False
|
2026-01-08 18:09:22 +08:00
|
|
|
|
except Exception as e:
|
2026-02-06 15:16:17 +08:00
|
|
|
|
print(f"通信错误: {e}")
|
|
|
|
|
|
self.is_connected = False
|
2026-01-08 18:09:22 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
def get_all_device_status(self, command_type: str='devices') -> dict[str, bool]:
|
2026-01-08 18:09:22 +08:00
|
|
|
|
"""
|
|
|
|
|
|
获取所有设备/传感器状态
|
|
|
|
|
|
:param command_type: 'devices'(控件) / 'sensors'(传感器)
|
|
|
|
|
|
:return: 状态字典 {设备名: 状态(bool)}
|
|
|
|
|
|
"""
|
|
|
|
|
|
command = read_status_command.get(command_type)
|
|
|
|
|
|
if not command:
|
2026-02-06 15:16:17 +08:00
|
|
|
|
print(f"未知的读取类型: {command_type}")
|
2026-01-08 18:09:22 +08:00
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
response = self.send_command(command)
|
|
|
|
|
|
status_dict = {}
|
|
|
|
|
|
|
|
|
|
|
|
if response and len(response) >= 10:
|
|
|
|
|
|
status_byte = response[9] # 状态在第10字节
|
|
|
|
|
|
status_bin = f"{status_byte:08b}"[::-1]
|
|
|
|
|
|
|
|
|
|
|
|
if command_type == 'devices':
|
|
|
|
|
|
bit_map = device_bit_map
|
|
|
|
|
|
name_map = device_name_map
|
|
|
|
|
|
elif command_type == 'sensors':
|
|
|
|
|
|
bit_map = sensor_bit_map
|
|
|
|
|
|
name_map = sensor_name_map
|
|
|
|
|
|
else:
|
2026-02-06 15:16:17 +08:00
|
|
|
|
print("不支持的映射类型")
|
|
|
|
|
|
return {}
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
|
|
|
|
|
for key, bit_index in bit_map.items():
|
|
|
|
|
|
state = status_bin[bit_index] == '1'
|
|
|
|
|
|
status_dict[key] = state
|
|
|
|
|
|
# readable = "开启" if state else "关闭"
|
2026-02-06 15:16:17 +08:00
|
|
|
|
# print(f"{device.capitalize()} 状态: {readable}")
|
2026-01-08 18:09:22 +08:00
|
|
|
|
else:
|
2026-02-06 15:16:17 +08:00
|
|
|
|
print("读取状态失败或响应无效")
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
|
|
|
|
|
return status_dict
|
|
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
def set_device(self, device_name: str, state: bool):
|
2026-01-08 18:09:22 +08:00
|
|
|
|
"""
|
2026-02-06 15:16:17 +08:00
|
|
|
|
设置指定输出设备(DO)的开关状态
|
|
|
|
|
|
:param device_name: 设备名称
|
|
|
|
|
|
:param state: 目标状态 True--打开 FALSE--关闭
|
2026-01-08 18:09:22 +08:00
|
|
|
|
"""
|
2026-02-06 15:16:17 +08:00
|
|
|
|
if device_name not in valve_commands:
|
|
|
|
|
|
raise ValueError(f"未知设备:{device_name}")
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
try:
|
|
|
|
|
|
current = self.get_all_device_status()
|
|
|
|
|
|
is_on = current.get(device_name, False)
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
if state and not is_on:
|
|
|
|
|
|
self.send_command(valve_commands[device_name]['open'])
|
|
|
|
|
|
elif not state and is_on:
|
|
|
|
|
|
self.send_command(valve_commands[device_name]['close'])
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise RuntimeError(f"设置设备 '{device_name}' 状态失败: {e}") from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 全局变量
|
2026-02-26 15:42:22 +08:00
|
|
|
|
# private
|
|
|
|
|
|
_GLOBAL_RELAY = RelayController()
|
2026-02-06 15:16:17 +08:00
|
|
|
|
|
2026-03-24 16:52:10 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
def ng_push():
|
|
|
|
|
|
"""NG推料流程"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 同时打开电磁阀1、2
|
|
|
|
|
|
write_do(SOLENOID_VALVE1, True)
|
|
|
|
|
|
write_do(SOLENOID_VALVE2, True)
|
|
|
|
|
|
print(f"电磁阀1、2已打开")
|
|
|
|
|
|
|
|
|
|
|
|
# 等待线条掉落
|
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
|
|
write_do(SOLENOID_VALVE1, False)
|
|
|
|
|
|
write_do(SOLENOID_VALVE2, False)
|
|
|
|
|
|
print(f"电磁阀1、2已关闭")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"NG推料失败:{e}")
|
|
|
|
|
|
raise RuntimeError("NG推料流程异常") from e
|
|
|
|
|
|
|
2026-03-24 16:52:10 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
def write_do(device_name: str, state: bool):
|
2026-01-08 18:09:22 +08:00
|
|
|
|
"""
|
2026-02-06 15:16:17 +08:00
|
|
|
|
控制单个数字输出设备(DO)的开关状态
|
|
|
|
|
|
:param device_name: 设备名称
|
|
|
|
|
|
:param state: True:打开 False:关闭
|
2026-01-08 18:09:22 +08:00
|
|
|
|
"""
|
2026-02-26 15:42:22 +08:00
|
|
|
|
if _GLOBAL_RELAY is None:
|
2026-02-06 15:16:17 +08:00
|
|
|
|
raise ValueError("未初始化实例")
|
|
|
|
|
|
|
|
|
|
|
|
# 验证设备是否存在
|
|
|
|
|
|
if device_name not in device_bit_map:
|
|
|
|
|
|
valid_devices = list(device_bit_map.keys())
|
|
|
|
|
|
raise ValueError(f"无效的设备名 '{device_name}'。有效设备: {valid_devices}")
|
|
|
|
|
|
|
|
|
|
|
|
# 确保已连接
|
2026-02-26 15:42:22 +08:00
|
|
|
|
if not _GLOBAL_RELAY.is_connected:
|
|
|
|
|
|
if not _GLOBAL_RELAY.connect():
|
2026-02-06 15:16:17 +08:00
|
|
|
|
raise RuntimeError("无法连接到网络继电器")
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
2026-02-26 15:42:22 +08:00
|
|
|
|
_GLOBAL_RELAY.set_device(device_name, state)
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise RuntimeError(f"控制设备 '{device_name}' 失败: {e}")
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-03-24 16:52:10 +08:00
|
|
|
|
|
2026-02-06 15:16:17 +08:00
|
|
|
|
def read_all_io() -> dict[str, dict[str, bool]]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
读取所有DI(传感器)和DO(设备)状态
|
|
|
|
|
|
:return: {'devices': {...}, 'sensors': {...}}
|
|
|
|
|
|
"""
|
2026-02-26 15:42:22 +08:00
|
|
|
|
if _GLOBAL_RELAY is None:
|
2026-02-06 15:16:17 +08:00
|
|
|
|
raise ValueError("未初始化")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
2026-02-26 15:42:22 +08:00
|
|
|
|
devices = _GLOBAL_RELAY.get_all_device_status('devices')
|
|
|
|
|
|
sensors = _GLOBAL_RELAY.get_all_device_status('sensors')
|
2026-02-06 15:16:17 +08:00
|
|
|
|
return {'devices': devices, 'sensors': sensors}
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-02-06 15:16:17 +08:00
|
|
|
|
print(f"读取IO状态失败:{e}")
|
|
|
|
|
|
raise RuntimeError("读取IO失败") from e
|
2026-01-08 18:09:22 +08:00
|
|
|
|
|
2026-03-24 16:52:10 +08:00
|
|
|
|
|
2026-01-08 18:09:22 +08:00
|
|
|
|
# ------------测试接口-------------
|
|
|
|
|
|
if __name__ == '__main__':
|
2026-02-06 15:16:17 +08:00
|
|
|
|
|
|
|
|
|
|
write_do(SOLENOID_VALVE1, True)
|
|
|
|
|
|
|
|
|
|
|
|
time.sleep(5)
|
|
|
|
|
|
|
|
|
|
|
|
io_status = read_all_io()
|
|
|
|
|
|
for name, status in io_status['devices'].items():
|
|
|
|
|
|
status_str = "开启" if status else "关闭"
|
|
|
|
|
|
print(f"{device_name_map.get(name, name)}: {status_str}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|