添加托盘类型配置表和相关UI功能,更新主窗口以支持托盘类型选择,增强设置界面以管理托盘类型,修改配置文件以包含Modbus设置。
This commit is contained in:
parent
b51848e2af
commit
c36189f255
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,4 +30,5 @@ htmlcov/
|
||||
# 构建文件
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
*.egg-info/
|
||||
config/app_config.json.bak
|
||||
|
||||
@ -22,5 +22,9 @@
|
||||
"default_exposure": 20000,
|
||||
"default_gain": 10,
|
||||
"default_framerate": 30
|
||||
},
|
||||
"modbus": {
|
||||
"host": "localhost",
|
||||
"port": "5020"
|
||||
}
|
||||
}
|
||||
293
dao/pallet_type_dao.py
Normal file
293
dao/pallet_type_dao.py
Normal file
@ -0,0 +1,293 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from utils.sql_utils import SQLUtils
|
||||
|
||||
class PalletTypeDAO:
|
||||
"""托盘类型数据访问对象"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化数据访问对象"""
|
||||
self.db = SQLUtils('sqlite', database='db/jtDB.db')
|
||||
|
||||
def __del__(self):
|
||||
"""析构函数,确保数据库连接关闭"""
|
||||
if hasattr(self, 'db'):
|
||||
self.db.close()
|
||||
|
||||
def get_all_pallet_types(self, include_disabled=False):
|
||||
"""获取所有托盘类型
|
||||
|
||||
Args:
|
||||
include_disabled: 是否包含禁用的类型
|
||||
|
||||
Returns:
|
||||
list: 托盘类型列表
|
||||
"""
|
||||
try:
|
||||
if include_disabled:
|
||||
sql = """
|
||||
SELECT id, type_name, operation_type, description, enabled, sort_order
|
||||
FROM pallet_types
|
||||
WHERE is_deleted = FALSE
|
||||
ORDER BY sort_order
|
||||
"""
|
||||
params = ()
|
||||
else:
|
||||
sql = """
|
||||
SELECT id, type_name, operation_type, description, enabled, sort_order
|
||||
FROM pallet_types
|
||||
WHERE is_deleted = FALSE AND enabled = TRUE
|
||||
ORDER BY sort_order
|
||||
"""
|
||||
params = ()
|
||||
|
||||
self.db.cursor.execute(sql, params)
|
||||
results = self.db.cursor.fetchall()
|
||||
|
||||
pallet_types = []
|
||||
for row in results:
|
||||
pallet_type = {
|
||||
'id': row[0],
|
||||
'type_name': row[1],
|
||||
'operation_type': row[2],
|
||||
'description': row[3],
|
||||
'enabled': bool(row[4]),
|
||||
'sort_order': row[5]
|
||||
}
|
||||
pallet_types.append(pallet_type)
|
||||
|
||||
return pallet_types
|
||||
except Exception as e:
|
||||
logging.error(f"获取托盘类型失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_pallet_types_by_operation(self, operation_type, include_disabled=False):
|
||||
"""根据操作类型获取托盘类型
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
include_disabled: 是否包含禁用的类型
|
||||
|
||||
Returns:
|
||||
list: 托盘类型列表
|
||||
"""
|
||||
try:
|
||||
if include_disabled:
|
||||
sql = """
|
||||
SELECT id, type_name, operation_type, description, enabled, sort_order
|
||||
FROM pallet_types
|
||||
WHERE operation_type = ? AND is_deleted = FALSE
|
||||
ORDER BY sort_order
|
||||
"""
|
||||
params = (operation_type,)
|
||||
else:
|
||||
sql = """
|
||||
SELECT id, type_name, operation_type, description, enabled, sort_order
|
||||
FROM pallet_types
|
||||
WHERE operation_type = ? AND is_deleted = FALSE AND enabled = TRUE
|
||||
ORDER BY sort_order
|
||||
"""
|
||||
params = (operation_type,)
|
||||
|
||||
self.db.cursor.execute(sql, params)
|
||||
results = self.db.cursor.fetchall()
|
||||
|
||||
pallet_types = []
|
||||
for row in results:
|
||||
pallet_type = {
|
||||
'id': row[0],
|
||||
'type_name': row[1],
|
||||
'operation_type': row[2],
|
||||
'description': row[3],
|
||||
'enabled': bool(row[4]),
|
||||
'sort_order': row[5]
|
||||
}
|
||||
pallet_types.append(pallet_type)
|
||||
|
||||
return pallet_types
|
||||
except Exception as e:
|
||||
logging.error(f"获取托盘类型失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_pallet_type_by_id(self, pallet_type_id):
|
||||
"""根据ID获取托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
|
||||
Returns:
|
||||
dict: 托盘类型信息,未找到则返回None
|
||||
"""
|
||||
try:
|
||||
sql = """
|
||||
SELECT id, type_name, operation_type, description, enabled, sort_order
|
||||
FROM pallet_types
|
||||
WHERE id = ? AND is_deleted = FALSE
|
||||
"""
|
||||
params = (pallet_type_id,)
|
||||
|
||||
self.db.cursor.execute(sql, params)
|
||||
row = self.db.cursor.fetchone()
|
||||
|
||||
if row:
|
||||
pallet_type = {
|
||||
'id': row[0],
|
||||
'type_name': row[1],
|
||||
'operation_type': row[2],
|
||||
'description': row[3],
|
||||
'enabled': bool(row[4]),
|
||||
'sort_order': row[5]
|
||||
}
|
||||
return pallet_type
|
||||
else:
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"获取托盘类型失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def create_pallet_type(self, data, username='system'):
|
||||
"""创建托盘类型
|
||||
|
||||
Args:
|
||||
data: 托盘类型数据
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
int: 新创建的托盘类型ID,失败返回None
|
||||
"""
|
||||
try:
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
sql = """
|
||||
INSERT INTO pallet_types (
|
||||
type_name, operation_type, description, enabled, sort_order,
|
||||
create_time, create_by, update_time, update_by, is_deleted
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
params = (
|
||||
data.get('type_name'),
|
||||
data.get('operation_type'),
|
||||
data.get('description', ''),
|
||||
data.get('enabled', True),
|
||||
data.get('sort_order', 999),
|
||||
current_time,
|
||||
username,
|
||||
current_time,
|
||||
username,
|
||||
False
|
||||
)
|
||||
|
||||
self.db.execute_update(sql, params)
|
||||
|
||||
# 获取新插入的ID
|
||||
self.db.cursor.execute("SELECT last_insert_rowid()")
|
||||
new_id = self.db.cursor.fetchone()[0]
|
||||
|
||||
return new_id
|
||||
except Exception as e:
|
||||
logging.error(f"创建托盘类型失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def update_pallet_type(self, pallet_type_id, data, username='system'):
|
||||
"""更新托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
data: 更新数据
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
bool: 更新是否成功
|
||||
"""
|
||||
try:
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 构建更新SQL
|
||||
update_fields = []
|
||||
params = []
|
||||
|
||||
# 可更新的字段
|
||||
allowed_fields = [
|
||||
'type_name', 'operation_type', 'description', 'enabled', 'sort_order'
|
||||
]
|
||||
|
||||
for field in allowed_fields:
|
||||
if field in data:
|
||||
update_fields.append(f"{field} = ?")
|
||||
params.append(data[field])
|
||||
|
||||
# 添加更新时间和更新人
|
||||
update_fields.append("update_time = ?")
|
||||
params.append(current_time)
|
||||
update_fields.append("update_by = ?")
|
||||
params.append(username)
|
||||
|
||||
# 添加ID到参数列表
|
||||
params.append(pallet_type_id)
|
||||
|
||||
# 构建SQL
|
||||
sql = f"""
|
||||
UPDATE pallet_types
|
||||
SET {', '.join(update_fields)}
|
||||
WHERE id = ?
|
||||
"""
|
||||
|
||||
self.db.execute_update(sql, params)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"更新托盘类型失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def delete_pallet_type(self, pallet_type_id, username='system'):
|
||||
"""删除托盘类型(软删除)
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
bool: 删除是否成功
|
||||
"""
|
||||
try:
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
sql = """
|
||||
UPDATE pallet_types
|
||||
SET is_deleted = TRUE, update_time = ?, update_by = ?
|
||||
WHERE id = ?
|
||||
"""
|
||||
params = (current_time, username, pallet_type_id)
|
||||
|
||||
self.db.execute_update(sql, params)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"删除托盘类型失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def toggle_pallet_type(self, pallet_type_id, enabled, username='system'):
|
||||
"""启用或禁用托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
enabled: 是否启用
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
"""
|
||||
try:
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
sql = """
|
||||
UPDATE pallet_types
|
||||
SET enabled = ?, update_time = ?, update_by = ?
|
||||
WHERE id = ? AND is_deleted = FALSE
|
||||
"""
|
||||
params = (enabled, current_time, username, pallet_type_id)
|
||||
|
||||
self.db.execute_update(sql, params)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"更新托盘类型启用状态失败: {str(e)}")
|
||||
return False
|
||||
BIN
db/jtDB.db
BIN
db/jtDB.db
Binary file not shown.
@ -74,5 +74,31 @@ create table if not exists inspection_pack_data
|
||||
update_time TIMESTAMP,
|
||||
update_by VARCHAR(50),
|
||||
is_deleted BOOLEAN
|
||||
)
|
||||
);
|
||||
|
||||
-- 创建托盘类型配置表
|
||||
CREATE TABLE IF NOT EXISTS pallet_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type_name VARCHAR(50) NOT NULL, -- 托盘类型名称
|
||||
operation_type VARCHAR(20) NOT NULL, -- 操作类型: input(上料), output(下料)
|
||||
description TEXT, -- 描述
|
||||
enabled BOOLEAN DEFAULT TRUE, -- 是否启用
|
||||
sort_order INTEGER NOT NULL, -- 排序顺序
|
||||
create_time TIMESTAMP NOT NULL,
|
||||
create_by VARCHAR(50) NOT NULL,
|
||||
update_time TIMESTAMP,
|
||||
update_by VARCHAR(50),
|
||||
is_deleted BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
|
||||
-- 插入默认托盘类型数据
|
||||
INSERT OR IGNORE INTO pallet_types (
|
||||
type_name, operation_type, description, enabled, sort_order, create_time, create_by
|
||||
) VALUES
|
||||
('标准托盘', 'input', '标准上料托盘', TRUE, 1, CURRENT_TIMESTAMP, 'system'),
|
||||
('小型托盘', 'input', '小型上料托盘', TRUE, 2, CURRENT_TIMESTAMP, 'system'),
|
||||
('大型托盘', 'input', '大型上料托盘', TRUE, 3, CURRENT_TIMESTAMP, 'system'),
|
||||
('标准托盘', 'output', '标准下料托盘', TRUE, 4, CURRENT_TIMESTAMP, 'system'),
|
||||
('小型托盘', 'output', '小型下料托盘', TRUE, 5, CURRENT_TIMESTAMP, 'system'),
|
||||
('大型托盘', 'output', '大型下料托盘', TRUE, 6, CURRENT_TIMESTAMP, 'system');
|
||||
|
||||
|
||||
171
modbus_register_tester.py
Normal file
171
modbus_register_tester.py
Normal file
@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Modbus Register Tester
|
||||
|
||||
A simple utility to test reading and writing to Modbus registers.
|
||||
This can be used to verify that the virtual Modbus server is working correctly.
|
||||
|
||||
Usage:
|
||||
python modbus_register_tester.py [--host HOST] [--port PORT] [--action {read|write}]
|
||||
[--address ADDRESS] [--value VALUE] [--count COUNT]
|
||||
|
||||
Options:
|
||||
--host HOST Modbus server host [default: localhost]
|
||||
--port PORT Modbus server port [default: 5020]
|
||||
--action {read|write} Action to perform [default: read]
|
||||
--address ADDRESS Register address to read/write [default: 1]
|
||||
--value VALUE Value to write (only for write action)
|
||||
--count COUNT Number of registers to read (only for read action) [default: 1]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def read_registers(client, address, count=1):
|
||||
"""Read holding registers
|
||||
|
||||
Args:
|
||||
client: Modbus client
|
||||
address: Register address
|
||||
count: Number of registers to read
|
||||
|
||||
Returns:
|
||||
list: Register values
|
||||
"""
|
||||
try:
|
||||
result = client.read_holding_registers(address=address, count=count)
|
||||
if result.isError():
|
||||
logger.error(f"读取寄存器D{address}失败: {result}")
|
||||
return None
|
||||
|
||||
logger.info(f"读取寄存器D{address}-D{address+count-1}成功: {result.registers}")
|
||||
return result.registers
|
||||
except Exception as e:
|
||||
logger.error(f"读取寄存器D{address}时发生错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def write_register(client, address, value):
|
||||
"""Write to a holding register
|
||||
|
||||
Args:
|
||||
client: Modbus client
|
||||
address: Register address
|
||||
value: Value to write
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
result = client.write_registers(address=address, values=[value])
|
||||
if result.isError():
|
||||
logger.error(f"写入寄存器D{address}值{value}失败: {result}")
|
||||
return False
|
||||
|
||||
logger.info(f"写入寄存器D{address}值{value}成功")
|
||||
|
||||
# Verify the write by reading back the value
|
||||
read_result = read_registers(client, address)
|
||||
if read_result and read_result[0] == value:
|
||||
logger.info(f"验证写入成功: D{address} = {value}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"验证写入失败: D{address} 预期 {value},实际 {read_result[0] if read_result else 'unknown'}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"写入寄存器D{address}值{value}时发生错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def connect_client(host, port):
|
||||
"""Connect to Modbus server
|
||||
|
||||
Args:
|
||||
host: Server host
|
||||
port: Server port
|
||||
|
||||
Returns:
|
||||
ModbusTcpClient: Connected client or None if failed
|
||||
"""
|
||||
try:
|
||||
client = ModbusTcpClient(host=host, port=port, timeout=10)
|
||||
logger.info(f"尝试连接到 Modbus 服务器 {host}:{port}")
|
||||
|
||||
is_connected = client.connect()
|
||||
if is_connected:
|
||||
logger.info(f"成功连接到 Modbus 服务器 {host}:{port}")
|
||||
return client
|
||||
else:
|
||||
logger.error(f"无法连接到 Modbus 服务器 {host}:{port}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"连接 Modbus 服务器 {host}:{port} 时发生错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def close_client(client):
|
||||
"""Close Modbus client connection
|
||||
|
||||
Args:
|
||||
client: Modbus client
|
||||
"""
|
||||
if client:
|
||||
client.close()
|
||||
logger.info("Modbus 客户端连接已关闭")
|
||||
|
||||
def parse_arguments():
|
||||
"""Parse command line arguments"""
|
||||
parser = argparse.ArgumentParser(description='Modbus Register Tester')
|
||||
parser.add_argument('--host', type=str, default='localhost',
|
||||
help='Modbus server host')
|
||||
parser.add_argument('--port', type=int, default=5020,
|
||||
help='Modbus server port')
|
||||
parser.add_argument('--action', type=str, choices=['read', 'write'],
|
||||
default='read', help='Action to perform')
|
||||
parser.add_argument('--address', type=int, default=1,
|
||||
help='Register address to read/write')
|
||||
parser.add_argument('--value', type=int,
|
||||
help='Value to write (only for write action)')
|
||||
parser.add_argument('--count', type=int, default=1,
|
||||
help='Number of registers to read (only for read action)')
|
||||
return parser.parse_args()
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parse_arguments()
|
||||
|
||||
# Validate arguments
|
||||
if args.action == 'write' and args.value is None:
|
||||
logger.error("写入操作需要指定 --value 参数")
|
||||
sys.exit(1)
|
||||
|
||||
client = None
|
||||
try:
|
||||
# Connect to the server
|
||||
client = connect_client(args.host, args.port)
|
||||
if not client:
|
||||
sys.exit(1)
|
||||
|
||||
# Perform the requested action
|
||||
if args.action == 'read':
|
||||
result = read_registers(client, args.address, args.count)
|
||||
if result is None:
|
||||
sys.exit(1)
|
||||
else: # write
|
||||
success = write_register(client, args.address, args.value)
|
||||
if not success:
|
||||
sys.exit(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("操作被用户中断")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error(f"发生错误: {str(e)}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
close_client(client)
|
||||
76
modbus_server.py
Normal file
76
modbus_server.py
Normal file
@ -0,0 +1,76 @@
|
||||
from pymodbus.server import StartTcpServer
|
||||
from pymodbus.datastore import ModbusSequentialDataBlock
|
||||
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ModbusServer:
|
||||
def __init__(self, host="localhost", port=5020):
|
||||
self.host = host
|
||||
self.port = port
|
||||
# 创建数据存储
|
||||
self.store = ModbusSlaveContext(
|
||||
di=ModbusSequentialDataBlock(0, [0] * 8000), # 离散输入
|
||||
co=ModbusSequentialDataBlock(0, [0] * 8000), # 线圈
|
||||
hr=ModbusSequentialDataBlock(0, [0] * 8000), # 保持寄存器
|
||||
ir=ModbusSequentialDataBlock(0, [0] * 8000), # 输入寄存器
|
||||
)
|
||||
self.context = ModbusServerContext(slaves=self.store, single=True)
|
||||
|
||||
# 初始化一些默认值
|
||||
self.store.setValues(3, 20, [0]) # D20 初始为手动模式
|
||||
self.store.setValues(3, 21, [0]) # D21 初始为未启动
|
||||
self.store.setValues(3, 22, [0]) # D22 初始为未锁定
|
||||
|
||||
def start(self):
|
||||
"""启动服务器"""
|
||||
logger.info(f"启动 Modbus TCP 服务器于 {self.host}:{self.port}")
|
||||
self.server_thread = threading.Thread(
|
||||
target=StartTcpServer,
|
||||
args=(self.context,),
|
||||
kwargs={'address': (self.host, self.port)}
|
||||
)
|
||||
self.server_thread.daemon = True
|
||||
self.server_thread.start()
|
||||
|
||||
def set_auto_mode(self):
|
||||
"""设置为自动模式"""
|
||||
self.store.setValues(3, 20, [2])
|
||||
logger.info("已设置为自动模式")
|
||||
|
||||
def set_manual_mode(self):
|
||||
"""设置为手动模式"""
|
||||
self.store.setValues(3, 20, [0])
|
||||
logger.info("已设置为手动模式")
|
||||
|
||||
def get_register_value(self, address):
|
||||
"""获取寄存器值"""
|
||||
values = self.store.getValues(3, address, 1)
|
||||
return values[0]
|
||||
|
||||
def print_status(self):
|
||||
"""打印当前状态"""
|
||||
mode = "自动" if self.get_register_value(20) == 2 else "手动"
|
||||
running = "是" if self.get_register_value(22) == 1 else "否"
|
||||
logger.info(f"当前模式: {mode}")
|
||||
logger.info(f"是否运行中: {running}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 创建并启动服务器
|
||||
server = ModbusServer()
|
||||
server.start()
|
||||
|
||||
# 设置为自动模式
|
||||
server.set_auto_mode()
|
||||
|
||||
try:
|
||||
while True:
|
||||
server.print_status()
|
||||
time.sleep(5) # 每5秒打印一次状态
|
||||
except KeyboardInterrupt:
|
||||
logger.info("服务器停止运行")
|
||||
375
ui/pallet_type_settings_ui.py
Normal file
375
ui/pallet_type_settings_ui.py
Normal file
@ -0,0 +1,375 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QLabel,
|
||||
QLineEdit, QCheckBox, QComboBox, QPushButton, QGroupBox,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView,
|
||||
QSpinBox, QDoubleSpinBox, QFrame, QScrollArea, QStackedWidget
|
||||
)
|
||||
from PySide6.QtGui import QFont, QBrush, QColor
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
|
||||
class PalletTypeSettingsUI(QWidget):
|
||||
"""托盘类型设置UI"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化UI"""
|
||||
# 设置字体
|
||||
self.title_font = QFont("微软雅黑", 14, QFont.Bold)
|
||||
self.normal_font = QFont("微软雅黑", 11)
|
||||
self.small_font = QFont("微软雅黑", 9)
|
||||
|
||||
# 设置背景颜色,便于识别
|
||||
self.setStyleSheet("background-color: #f5f5f5;")
|
||||
|
||||
# 创建主布局
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(20, 20, 20, 20)
|
||||
self.main_layout.setSpacing(15)
|
||||
|
||||
# 标题
|
||||
self.title_label = QLabel("托盘类型配置")
|
||||
self.title_label.setFont(self.title_font)
|
||||
self.title_label.setAlignment(Qt.AlignCenter)
|
||||
self.title_label.setStyleSheet("color: #1a237e; padding: 10px;")
|
||||
self.main_layout.addWidget(self.title_label)
|
||||
|
||||
# 说明文本
|
||||
self.desc_label = QLabel("配置上料和下料托盘类型,点击上料/下料按钮切换显示对应类型。")
|
||||
self.desc_label.setWordWrap(True)
|
||||
self.desc_label.setStyleSheet("color: #666666; padding: 0px 10px 10px 10px;")
|
||||
self.main_layout.addWidget(self.desc_label)
|
||||
|
||||
# 创建操作类型选择按钮
|
||||
self.operation_layout = QHBoxLayout()
|
||||
self.operation_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.operation_layout.setSpacing(20)
|
||||
|
||||
self.input_button = QPushButton("上料类型")
|
||||
self.input_button.setFont(self.normal_font)
|
||||
self.input_button.setFixedHeight(40)
|
||||
self.input_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #1e88e5;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #1976d2;
|
||||
}
|
||||
QPushButton:checked {
|
||||
background-color: #1565c0;
|
||||
border: 2px solid #0d47a1;
|
||||
}
|
||||
""")
|
||||
self.input_button.setCheckable(True)
|
||||
self.input_button.setChecked(True)
|
||||
|
||||
self.output_button = QPushButton("下料类型")
|
||||
self.output_button.setFont(self.normal_font)
|
||||
self.output_button.setFixedHeight(40)
|
||||
self.output_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #fb8c00;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #f57c00;
|
||||
}
|
||||
QPushButton:checked {
|
||||
background-color: #ef6c00;
|
||||
border: 2px solid #e65100;
|
||||
}
|
||||
""")
|
||||
self.output_button.setCheckable(True)
|
||||
|
||||
self.operation_layout.addWidget(self.input_button)
|
||||
self.operation_layout.addWidget(self.output_button)
|
||||
self.main_layout.addLayout(self.operation_layout)
|
||||
|
||||
# 创建堆叠部件,用于切换上料和下料类型配置
|
||||
self.stacked_widget = QStackedWidget()
|
||||
|
||||
# 创建上料类型配置页面
|
||||
self.input_widget = self.create_pallet_type_widget("input")
|
||||
self.stacked_widget.addWidget(self.input_widget)
|
||||
|
||||
# 创建下料类型配置页面
|
||||
self.output_widget = self.create_pallet_type_widget("output")
|
||||
self.stacked_widget.addWidget(self.output_widget)
|
||||
|
||||
# 默认显示上料类型
|
||||
self.stacked_widget.setCurrentIndex(0)
|
||||
|
||||
self.main_layout.addWidget(self.stacked_widget, 1)
|
||||
|
||||
# 底部按钮区域
|
||||
self.button_layout = QHBoxLayout()
|
||||
self.button_layout.setContentsMargins(0, 10, 0, 0)
|
||||
|
||||
self.save_button = QPushButton("保存配置")
|
||||
self.save_button.setFont(self.normal_font)
|
||||
self.save_button.setFixedSize(120, 40)
|
||||
self.save_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #3d8b40;
|
||||
}
|
||||
""")
|
||||
|
||||
self.reset_button = QPushButton("重置")
|
||||
self.reset_button.setFont(self.normal_font)
|
||||
self.reset_button.setFixedSize(120, 40)
|
||||
self.reset_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #e53935;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
""")
|
||||
|
||||
self.button_layout.addStretch()
|
||||
self.button_layout.addWidget(self.reset_button)
|
||||
self.button_layout.addSpacing(20)
|
||||
self.button_layout.addWidget(self.save_button)
|
||||
|
||||
self.main_layout.addLayout(self.button_layout)
|
||||
|
||||
# 连接信号和槽
|
||||
self.input_button.clicked.connect(self.show_input_types)
|
||||
self.output_button.clicked.connect(self.show_output_types)
|
||||
|
||||
def create_pallet_type_widget(self, operation_type):
|
||||
"""创建托盘类型配置部件
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
|
||||
Returns:
|
||||
QWidget: 托盘类型配置部件
|
||||
"""
|
||||
widget = QWidget()
|
||||
layout = QVBoxLayout(widget)
|
||||
layout.setContentsMargins(0, 10, 0, 0)
|
||||
layout.setSpacing(15)
|
||||
|
||||
# 创建表格
|
||||
table = QTableWidget()
|
||||
table.setFont(self.normal_font)
|
||||
table.setColumnCount(4)
|
||||
table.setHorizontalHeaderLabels(["类型名称", "描述", "排序", "启用"])
|
||||
table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||
table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
||||
table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
||||
table.verticalHeader().setVisible(False)
|
||||
table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
table.setAlternatingRowColors(True)
|
||||
table.setStyleSheet("""
|
||||
QTableWidget {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background-color: #ffffff;
|
||||
alternate-background-color: #f5f5f5;
|
||||
}
|
||||
QHeaderView::section {
|
||||
background-color: #f0f0f0;
|
||||
padding: 6px;
|
||||
border: 1px solid #ddd;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
|
||||
# 设置表格属性,用于标识操作类型
|
||||
table.setObjectName(f"{operation_type}_table")
|
||||
table.setProperty("operation_type", operation_type)
|
||||
|
||||
layout.addWidget(table)
|
||||
|
||||
# 创建表单布局,用于添加/编辑托盘类型
|
||||
form_group = QGroupBox("添加/编辑托盘类型")
|
||||
form_group.setFont(self.normal_font)
|
||||
form_layout = QFormLayout(form_group)
|
||||
form_layout.setContentsMargins(15, 25, 15, 15)
|
||||
form_layout.setSpacing(10)
|
||||
|
||||
# 类型名称
|
||||
type_name_label = QLabel("类型名称:")
|
||||
type_name_label.setFont(self.normal_font)
|
||||
type_name_input = QLineEdit()
|
||||
type_name_input.setFont(self.normal_font)
|
||||
type_name_input.setObjectName(f"{operation_type}_type_name_input")
|
||||
form_layout.addRow(type_name_label, type_name_input)
|
||||
|
||||
# 描述
|
||||
desc_label = QLabel("描述:")
|
||||
desc_label.setFont(self.normal_font)
|
||||
desc_input = QLineEdit()
|
||||
desc_input.setFont(self.normal_font)
|
||||
desc_input.setObjectName(f"{operation_type}_desc_input")
|
||||
form_layout.addRow(desc_label, desc_input)
|
||||
|
||||
# 排序
|
||||
sort_order_label = QLabel("排序:")
|
||||
sort_order_label.setFont(self.normal_font)
|
||||
sort_order_spin = QSpinBox()
|
||||
sort_order_spin.setFont(self.normal_font)
|
||||
sort_order_spin.setObjectName(f"{operation_type}_sort_order_spin")
|
||||
sort_order_spin.setRange(1, 999)
|
||||
sort_order_spin.setValue(100)
|
||||
form_layout.addRow(sort_order_label, sort_order_spin)
|
||||
|
||||
# 是否启用
|
||||
enabled_check = QCheckBox("启用")
|
||||
enabled_check.setFont(self.normal_font)
|
||||
enabled_check.setObjectName(f"{operation_type}_enabled_check")
|
||||
enabled_check.setChecked(True)
|
||||
form_layout.addRow("", enabled_check)
|
||||
|
||||
# 添加表单按钮
|
||||
form_button_layout = QHBoxLayout()
|
||||
|
||||
add_button = QPushButton("添加")
|
||||
add_button.setFont(self.normal_font)
|
||||
add_button.setObjectName(f"{operation_type}_add_button")
|
||||
add_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #3d8b40;
|
||||
}
|
||||
""")
|
||||
|
||||
update_button = QPushButton("更新")
|
||||
update_button.setFont(self.normal_font)
|
||||
update_button.setObjectName(f"{operation_type}_update_button")
|
||||
update_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #1e88e5;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #1976d2;
|
||||
}
|
||||
""")
|
||||
update_button.setEnabled(False)
|
||||
|
||||
delete_button = QPushButton("删除")
|
||||
delete_button.setFont(self.normal_font)
|
||||
delete_button.setObjectName(f"{operation_type}_delete_button")
|
||||
delete_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #e53935;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
""")
|
||||
delete_button.setEnabled(False)
|
||||
|
||||
cancel_button = QPushButton("取消")
|
||||
cancel_button.setFont(self.normal_font)
|
||||
cancel_button.setObjectName(f"{operation_type}_cancel_button")
|
||||
cancel_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #9e9e9e;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #757575;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #616161;
|
||||
}
|
||||
""")
|
||||
cancel_button.setEnabled(False)
|
||||
|
||||
form_button_layout.addWidget(add_button)
|
||||
form_button_layout.addWidget(update_button)
|
||||
form_button_layout.addWidget(delete_button)
|
||||
form_button_layout.addWidget(cancel_button)
|
||||
|
||||
form_layout.addRow("", form_button_layout)
|
||||
|
||||
layout.addWidget(form_group)
|
||||
|
||||
# 添加隐藏字段,用于存储当前编辑的ID
|
||||
widget.setProperty("current_edit_id", -1)
|
||||
|
||||
return widget
|
||||
|
||||
def show_input_types(self):
|
||||
"""显示上料类型"""
|
||||
self.input_button.setChecked(True)
|
||||
self.output_button.setChecked(False)
|
||||
self.stacked_widget.setCurrentIndex(0)
|
||||
|
||||
def show_output_types(self):
|
||||
"""显示下料类型"""
|
||||
self.input_button.setChecked(False)
|
||||
self.output_button.setChecked(True)
|
||||
self.stacked_widget.setCurrentIndex(1)
|
||||
|
||||
def set_form_enabled(self, enabled):
|
||||
"""设置表单是否可编辑"""
|
||||
self.input_button.setEnabled(enabled)
|
||||
self.output_button.setEnabled(enabled)
|
||||
self.save_button.setEnabled(enabled)
|
||||
self.reset_button.setEnabled(enabled)
|
||||
|
||||
# 禁用所有表格和表单
|
||||
for widget in [self.input_widget, self.output_widget]:
|
||||
for child in widget.findChildren(QWidget):
|
||||
child.setEnabled(enabled)
|
||||
@ -1,14 +1,15 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QLabel, QLineEdit, QPushButton, QComboBox, QGridLayout, QHBoxLayout, QVBoxLayout,
|
||||
QTabWidget, QFrame, QFormLayout, QGroupBox, QRadioButton, QSpacerItem, QSizePolicy,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView, QSlider
|
||||
QTableWidget, QTableWidgetItem, QHeaderView, QSlider, QCheckBox
|
||||
)
|
||||
from PySide6.QtGui import QFont
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QFont, QBrush, QColor
|
||||
from PySide6.QtCore import Qt, Signal, QSize
|
||||
|
||||
class SettingsUI(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
@ -42,6 +43,7 @@ class SettingsUI(QWidget):
|
||||
self.create_auth_tab()
|
||||
self.create_user_tab()
|
||||
self.create_param_tab()
|
||||
self.create_pallet_type_tab()
|
||||
|
||||
def create_camera_tab(self):
|
||||
# 相机设置选项卡
|
||||
@ -208,14 +210,14 @@ class SettingsUI(QWidget):
|
||||
self.db_type_group.setFont(self.normal_font)
|
||||
self.db_type_layout = QHBoxLayout()
|
||||
|
||||
self.sqlite_radio = QRadioButton("SQLite")
|
||||
self.sqlite_radio = QCheckBox("SQLite")
|
||||
self.sqlite_radio.setFont(self.normal_font)
|
||||
self.sqlite_radio.setChecked(True)
|
||||
|
||||
self.pgsql_radio = QRadioButton("PostgreSQL")
|
||||
self.pgsql_radio = QCheckBox("PostgreSQL")
|
||||
self.pgsql_radio.setFont(self.normal_font)
|
||||
|
||||
self.mysql_radio = QRadioButton("MySQL")
|
||||
self.mysql_radio = QCheckBox("MySQL")
|
||||
self.mysql_radio.setFont(self.normal_font)
|
||||
|
||||
self.db_type_layout.addWidget(self.sqlite_radio)
|
||||
@ -385,4 +387,24 @@ class SettingsUI(QWidget):
|
||||
self.param_layout.addWidget(self.param_placeholder)
|
||||
self.param_layout.addStretch(1)
|
||||
|
||||
self.tab_widget.addTab(self.param_tab, "参数配置")
|
||||
self.tab_widget.addTab(self.param_tab, "参数配置")
|
||||
|
||||
def create_pallet_type_tab(self):
|
||||
# 托盘类型设置选项卡
|
||||
self.pallet_type_tab = QWidget()
|
||||
self.pallet_type_layout = QVBoxLayout(self.pallet_type_tab)
|
||||
self.pallet_type_layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
# 占位标签
|
||||
self.pallet_type_placeholder = QLabel("正在加载托盘类型设置...")
|
||||
self.pallet_type_placeholder.setFont(self.normal_font)
|
||||
self.pallet_type_placeholder.setAlignment(Qt.AlignCenter)
|
||||
self.pallet_type_placeholder.setStyleSheet("color: #888888; padding: 20px;")
|
||||
self.pallet_type_layout.addWidget(self.pallet_type_placeholder)
|
||||
|
||||
self.tab_widget.addTab(self.pallet_type_tab, "托盘类型")
|
||||
|
||||
def back_to_main(self):
|
||||
"""返回主页"""
|
||||
if self.parent and hasattr(self.parent, 'show_main_page'):
|
||||
self.parent.show_main_page()
|
||||
209
utils/modbus_utils.py
Normal file
209
utils/modbus_utils.py
Normal file
@ -0,0 +1,209 @@
|
||||
from flask import Flask, jsonify, request, make_response
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
import time
|
||||
import logging
|
||||
from .config_loader import ConfigLoader
|
||||
# 配置 Flask 日志级别
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.WARNING)
|
||||
|
||||
|
||||
# Modbus TCP 配置
|
||||
|
||||
class ModbusUtils:
|
||||
|
||||
def __init__(self) -> None:
|
||||
# 初始化 modbus 配置
|
||||
config = ConfigLoader.get_instance()
|
||||
self.MODBUS_HOST = config.get_value("modbus.host")
|
||||
self.MODBUS_PORT = config.get_value("modbus.port")
|
||||
|
||||
def get_client(self):
|
||||
# 创建Modbus TCP客户端实例,指定服务器的IP地址和端口号
|
||||
# client = ModbusTcpClient('localhost', port=5020)
|
||||
client = ModbusTcpClient(self.MODBUS_HOST, port=self.MODBUS_PORT, timeout=10) # 增加超时时间
|
||||
logging.info(f"Attempting to connect to Modbus server {self.MODBUS_HOST}:{self.MODBUS_PORT}")
|
||||
try:
|
||||
is_connected = client.connect() #确保客户端已连接
|
||||
if is_connected:
|
||||
logging.info(f"Successfully connected to Modbus server {self.MODBUS_HOST}:{self.MODBUS_PORT}")
|
||||
else:
|
||||
logging.error(f"Failed to connect to Modbus server {self.MODBUS_HOST}:{self.MODBUS_PORT}. client.connect() returned False.")
|
||||
except Exception as e:
|
||||
logging.error(f"Exception during connection to Modbus server {self.MODBUS_HOST}:{self.MODBUS_PORT}: {e}")
|
||||
# Optionally, re-raise or handle as appropriate
|
||||
return None # Or raise an exception
|
||||
return client
|
||||
|
||||
|
||||
def close_client(self, client):
|
||||
# 关闭客户端连接
|
||||
if client:
|
||||
client.close()
|
||||
|
||||
# 新增十进制转成二进制
|
||||
@staticmethod
|
||||
def decimal_to_binary(decimal):
|
||||
"""十进制转16位二进制,右对齐"""
|
||||
return format(decimal, '016b')
|
||||
|
||||
# 新增二进制转成十进制
|
||||
@staticmethod
|
||||
def binary_to_decimal(binary):
|
||||
"""二进制字符串转十进制"""
|
||||
return int(binary, 2)
|
||||
|
||||
# 新增十进制转16进制
|
||||
@staticmethod
|
||||
def decimal_to_hex(decimal):
|
||||
return hex(decimal)[2:]
|
||||
|
||||
|
||||
@staticmethod
|
||||
def split_data(data):
|
||||
'''
|
||||
解析位置编码获取寄存器地址
|
||||
:param data: 位置编码,例如 "01-003-02"
|
||||
:return: 寄存器地址
|
||||
|
||||
规则说明:
|
||||
- column: 库区编号,01表示A库(D100-D108),02表示B库(D110-D118)
|
||||
- level: 层号(1-9),对应寄存器最后一位,如第2层对应D101
|
||||
- row: 排号(1-16),对应寄存器的位索引
|
||||
'''
|
||||
data_list = data.split('-')
|
||||
column = data_list[0]
|
||||
row = data_list[1]
|
||||
level = data_list[2]
|
||||
return column, row, level
|
||||
|
||||
def get_hex_str(self, data):
|
||||
"""
|
||||
解析位置编码获取寄存器地址
|
||||
:param data: 位置编码,例如 "01-003-02"
|
||||
:return: 寄存器地址
|
||||
|
||||
规则说明:
|
||||
- column: 库区编号,01表示A库(D100-D108),02表示B库(D110-D118)
|
||||
- level: 层号(1-9),对应寄存器最后一位,如第2层对应D101
|
||||
- row: 排号(1-16),对应寄存器的位索引
|
||||
|
||||
示例:
|
||||
01-003-02 => D101.2 (A库第2层,第3排)
|
||||
02-005-06 => D115.4 (B库第6层,第5排)
|
||||
"""
|
||||
column, row, level = self.split_data(data)
|
||||
# 计算基础地址:A库从100开始,B库从110开始
|
||||
base_address = 100 + (int(column) - 1) * 10
|
||||
# 加上层号得到最终地址
|
||||
register_address = base_address + (int(level) - 1)
|
||||
return register_address
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_register_value(client, address, expected_value, timeout_seconds=5):
|
||||
"""
|
||||
校验指定寄存器地址中的数据是否达到期望值
|
||||
:param client: ModbusTcp客户端
|
||||
:param address: 寄存器地址
|
||||
:param expected_value: 期望值
|
||||
:param timeout_seconds: 超时时间(秒)
|
||||
:return: True表示达到期望值,False表示超时
|
||||
"""
|
||||
try:
|
||||
start_time = time.time()
|
||||
while True:
|
||||
result = client.read_holding_registers(address=address, count=1)
|
||||
if not result.isError() and result.registers[0] == expected_value:
|
||||
logging.info(f"寄存器D{address}值: {result.registers[0]} 达到期望值{expected_value}")
|
||||
return True
|
||||
|
||||
if time.time() - start_time > timeout_seconds:
|
||||
logging.warning(f"寄存器D{address} {timeout_seconds}秒内未达到期望值{expected_value}")
|
||||
return False
|
||||
|
||||
time.sleep(0.2) # 添加短暂延时,避免过于频繁的读取
|
||||
except Exception as e:
|
||||
logging.error(f"校验寄存器D{address}值时发生错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def write_register_until_success(self, client, address, value, expected_value=None, timeout_seconds=5):
|
||||
"""
|
||||
循环写入寄存器直到成功或超时
|
||||
:param client: ModbusTcp客户端
|
||||
:param address: 寄存器地址
|
||||
:param value: 要写入的值
|
||||
:param expected_value: 期望值(如果为None,则使用value作为期望值)
|
||||
:param timeout_seconds: 超时时间(秒)
|
||||
:return: True表示写入成功,False表示超时
|
||||
"""
|
||||
if expected_value is None:
|
||||
expected_value = value
|
||||
|
||||
start_time = time.time()
|
||||
while True:
|
||||
# 写入值
|
||||
logging.info(f"写入寄存器D{address}值: {value}")
|
||||
client.write_registers(address=address, values=[value])
|
||||
logging.info(f"写入寄存器D{address}值: {value}成功")
|
||||
# 检查是否达到期望值
|
||||
if self.check_register_value(client, address, expected_value):
|
||||
return True
|
||||
|
||||
if time.time() - start_time > timeout_seconds:
|
||||
logging.warning(f"寄存器D{address} {timeout_seconds}秒内写入失败")
|
||||
return False
|
||||
|
||||
time.sleep(0.2) # 添加短暂延时,避免过于频繁的写入
|
||||
|
||||
|
||||
@staticmethod
|
||||
def handle_error(error_msg, task_number='', is_emergency_stop=False):
|
||||
"""通用错误处理函数
|
||||
:param error_msg: 错误信息
|
||||
:param task_number: 任务编号,默认为空
|
||||
:param is_emergency_stop: 是否是急停引起的错误
|
||||
"""
|
||||
logging.error(error_msg)
|
||||
pass
|
||||
# return response, 500
|
||||
|
||||
# 添加便捷方法,用于读取保持寄存器
|
||||
def read_holding_register(self, client, address, count=1):
|
||||
"""
|
||||
读取保持寄存器
|
||||
:param client: ModbusTcp客户端
|
||||
:param address: 寄存器地址
|
||||
:param count: 读取的寄存器数量
|
||||
:return: 读取结果,失败返回None
|
||||
"""
|
||||
try:
|
||||
result = client.read_holding_registers(address=address, count=count)
|
||||
if result.isError():
|
||||
logging.error(f"读取寄存器D{address}失败: {result}")
|
||||
return None
|
||||
return result.registers
|
||||
except Exception as e:
|
||||
logging.error(f"读取寄存器D{address}时发生错误: {str(e)}")
|
||||
return None
|
||||
|
||||
# 添加便捷方法,用于写入保持寄存器
|
||||
def write_register(self, client, address, value):
|
||||
"""
|
||||
写入保持寄存器
|
||||
:param client: ModbusTcp客户端
|
||||
:param address: 寄存器地址
|
||||
:param value: 要写入的值
|
||||
:return: 是否写入成功
|
||||
"""
|
||||
try:
|
||||
result = client.write_registers(address=address, values=[value])
|
||||
if result.isError():
|
||||
logging.error(f"写入寄存器D{address}值{value}失败: {result}")
|
||||
return False
|
||||
logging.info(f"写入寄存器D{address}值{value}成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"写入寄存器D{address}值{value}时发生错误: {str(e)}")
|
||||
return False
|
||||
|
||||
137
utils/pallet_type_manager.py
Normal file
137
utils/pallet_type_manager.py
Normal file
@ -0,0 +1,137 @@
|
||||
import logging
|
||||
from dao.pallet_type_dao import PalletTypeDAO
|
||||
|
||||
class PalletTypeManager:
|
||||
"""托盘类型管理器,用于管理托盘类型配置"""
|
||||
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
"""获取单例实例"""
|
||||
if cls._instance is None:
|
||||
cls._instance = PalletTypeManager()
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""初始化托盘类型管理器"""
|
||||
self.dao = PalletTypeDAO()
|
||||
self.pallet_types = []
|
||||
self.reload_pallet_types()
|
||||
|
||||
def reload_pallet_types(self):
|
||||
"""重新加载托盘类型"""
|
||||
try:
|
||||
self.pallet_types = self.dao.get_all_pallet_types(include_disabled=True)
|
||||
logging.info(f"已加载{len(self.pallet_types)}个托盘类型")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"加载托盘类型失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_all_pallet_types(self, include_disabled=False):
|
||||
"""获取所有托盘类型
|
||||
|
||||
Args:
|
||||
include_disabled: 是否包含禁用的类型
|
||||
|
||||
Returns:
|
||||
list: 托盘类型列表
|
||||
"""
|
||||
if include_disabled:
|
||||
return self.pallet_types
|
||||
else:
|
||||
return [pallet_type for pallet_type in self.pallet_types if pallet_type['enabled']]
|
||||
|
||||
def get_pallet_types_by_operation(self, operation_type, include_disabled=False):
|
||||
"""根据操作类型获取托盘类型
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
include_disabled: 是否包含禁用的类型
|
||||
|
||||
Returns:
|
||||
list: 托盘类型列表
|
||||
"""
|
||||
if include_disabled:
|
||||
return [pallet_type for pallet_type in self.pallet_types
|
||||
if pallet_type['operation_type'] == operation_type]
|
||||
else:
|
||||
return [pallet_type for pallet_type in self.pallet_types
|
||||
if pallet_type['operation_type'] == operation_type and pallet_type['enabled']]
|
||||
|
||||
def get_pallet_type_by_id(self, pallet_type_id):
|
||||
"""根据ID获取托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
|
||||
Returns:
|
||||
dict: 托盘类型信息,未找到则返回None
|
||||
"""
|
||||
for pallet_type in self.pallet_types:
|
||||
if pallet_type['id'] == pallet_type_id:
|
||||
return pallet_type
|
||||
return None
|
||||
|
||||
def create_pallet_type(self, data, username='system'):
|
||||
"""创建托盘类型
|
||||
|
||||
Args:
|
||||
data: 托盘类型数据
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
int: 新创建的托盘类型ID,失败返回None
|
||||
"""
|
||||
result = self.dao.create_pallet_type(data, username)
|
||||
if result:
|
||||
self.reload_pallet_types()
|
||||
return result
|
||||
|
||||
def update_pallet_type(self, pallet_type_id, data, username='system'):
|
||||
"""更新托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
data: 更新数据
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
bool: 更新是否成功
|
||||
"""
|
||||
result = self.dao.update_pallet_type(pallet_type_id, data, username)
|
||||
if result:
|
||||
self.reload_pallet_types()
|
||||
return result
|
||||
|
||||
def delete_pallet_type(self, pallet_type_id, username='system'):
|
||||
"""删除托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
bool: 删除是否成功
|
||||
"""
|
||||
result = self.dao.delete_pallet_type(pallet_type_id, username)
|
||||
if result:
|
||||
self.reload_pallet_types()
|
||||
return result
|
||||
|
||||
def toggle_pallet_type(self, pallet_type_id, enabled, username='system'):
|
||||
"""启用或禁用托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
enabled: 是否启用
|
||||
username: 操作用户
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
"""
|
||||
result = self.dao.toggle_pallet_type(pallet_type_id, enabled, username)
|
||||
if result:
|
||||
self.reload_pallet_types()
|
||||
return result
|
||||
@ -4,11 +4,11 @@ import logging
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from utils.modbus_utils import ModbusUtils
|
||||
# 导入PySide6
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QMessageBox, QTableWidgetItem, QStackedWidget, QLabel, QMainWindow,
|
||||
QTableWidget, QMenu
|
||||
QTableWidget, QMenu, QComboBox, QFormLayout, QDialog, QVBoxLayout, QHBoxLayout, QPushButton
|
||||
)
|
||||
from PySide6.QtCore import Qt, QTimer
|
||||
from PySide6.QtGui import QBrush, QColor
|
||||
@ -21,6 +21,8 @@ from widgets.camera_settings_widget import CameraSettingsWidget
|
||||
|
||||
# 导入检验配置管理器
|
||||
from utils.inspection_config_manager import InspectionConfigManager
|
||||
# 导入托盘类型管理器
|
||||
from utils.pallet_type_manager import PalletTypeManager
|
||||
|
||||
|
||||
class MainWindow(MainWindowUI):
|
||||
@ -45,6 +47,16 @@ class MainWindow(MainWindowUI):
|
||||
# 初始化检验配置管理器
|
||||
self.inspection_manager = InspectionConfigManager.get_instance()
|
||||
|
||||
# 初始化托盘类型管理器
|
||||
self.pallet_type_manager = PalletTypeManager.get_instance()
|
||||
|
||||
# 创建表单布局,用于添加托盘类型选择控件
|
||||
self.material_form_layout = QFormLayout()
|
||||
self.material_content_layout.addLayout(self.material_form_layout)
|
||||
|
||||
self.output_form_layout = QFormLayout()
|
||||
self.output_content_layout.addLayout(self.output_form_layout)
|
||||
|
||||
# 只有在相机启用时创建相机显示组件
|
||||
if self.camera_enabled:
|
||||
# 创建相机显示组件并添加到上料区
|
||||
@ -74,6 +86,9 @@ class MainWindow(MainWindowUI):
|
||||
# 设置中央部件为堆叠部件
|
||||
self.setCentralWidget(self.stacked_widget)
|
||||
|
||||
# 添加托盘类型选择下拉框
|
||||
self.add_pallet_type_selectors()
|
||||
|
||||
# 连接信号和槽
|
||||
self.connect_signals()
|
||||
|
||||
@ -94,6 +109,50 @@ class MainWindow(MainWindowUI):
|
||||
|
||||
logging.info(f"主窗口已创建,用户: {user_name}")
|
||||
|
||||
def add_pallet_type_selectors(self):
|
||||
"""添加托盘类型选择下拉框"""
|
||||
# 创建上料托盘类型选择下拉框
|
||||
self.input_pallet_type_label = QLabel("上料托盘类型:")
|
||||
self.input_pallet_type_label.setFont(self.normal_font)
|
||||
self.input_pallet_type_label.setVisible(False)
|
||||
self.material_form_layout.addRow(self.input_pallet_type_label)
|
||||
|
||||
self.input_pallet_type_combo = QComboBox()
|
||||
self.input_pallet_type_combo.setFont(self.normal_font)
|
||||
self.input_pallet_type_combo.setVisible(False)
|
||||
self.material_form_layout.addRow("", self.input_pallet_type_combo)
|
||||
|
||||
# 创建下料托盘类型选择下拉框
|
||||
self.output_pallet_type_label = QLabel("下料托盘类型:")
|
||||
self.output_pallet_type_label.setFont(self.normal_font)
|
||||
self.output_pallet_type_label.setVisible(False)
|
||||
self.output_form_layout.addRow(self.output_pallet_type_label)
|
||||
|
||||
self.output_pallet_type_combo = QComboBox()
|
||||
self.output_pallet_type_combo.setFont(self.normal_font)
|
||||
self.output_pallet_type_combo.setVisible(False)
|
||||
self.output_form_layout.addRow("", self.output_pallet_type_combo)
|
||||
|
||||
# 加载托盘类型数据
|
||||
self.update_pallet_types()
|
||||
|
||||
def update_pallet_types(self):
|
||||
"""更新托盘类型下拉框"""
|
||||
# 重新加载托盘类型数据
|
||||
self.pallet_type_manager.reload_pallet_types()
|
||||
|
||||
# 更新上料托盘类型
|
||||
input_types = self.pallet_type_manager.get_pallet_types_by_operation("input")
|
||||
self.input_pallet_type_combo.clear()
|
||||
for pallet_type in input_types:
|
||||
self.input_pallet_type_combo.addItem(pallet_type['type_name'], pallet_type['id'])
|
||||
|
||||
# 更新下料托盘类型
|
||||
output_types = self.pallet_type_manager.get_pallet_types_by_operation("output")
|
||||
self.output_pallet_type_combo.clear()
|
||||
for pallet_type in output_types:
|
||||
self.output_pallet_type_combo.addItem(pallet_type['type_name'], pallet_type['id'])
|
||||
|
||||
def load_config(self):
|
||||
"""加载配置文件"""
|
||||
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "app_config.json")
|
||||
@ -192,41 +251,161 @@ class MainWindow(MainWindowUI):
|
||||
|
||||
def handle_input(self):
|
||||
"""处理上料按钮点击事件"""
|
||||
logging.info("上料按钮被点击")
|
||||
# 创建对话框
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("上料操作")
|
||||
dialog.setFixedSize(400, 250)
|
||||
|
||||
QMessageBox.information(self, "操作提示", "开始上料操作")
|
||||
# 这里添加上料相关的业务逻辑
|
||||
# 对话框布局
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
# 添加提示信息
|
||||
info_label = QLabel("请选择上料托盘类型:")
|
||||
info_label.setFont(self.normal_font)
|
||||
layout.addWidget(info_label)
|
||||
|
||||
# 添加托盘类型选择
|
||||
pallet_combo = QComboBox()
|
||||
pallet_combo.setFont(self.normal_font)
|
||||
# 复制当前托盘类型选择器的内容
|
||||
for i in range(self.input_pallet_type_combo.count()):
|
||||
pallet_combo.addItem(self.input_pallet_type_combo.itemText(i))
|
||||
layout.addWidget(pallet_combo)
|
||||
|
||||
# 添加按钮
|
||||
button_layout = QHBoxLayout()
|
||||
confirm_button = QPushButton("确认")
|
||||
confirm_button.setFont(self.normal_font)
|
||||
confirm_button.setStyleSheet("background-color: #e3f2fd; border: 1px solid #2196f3; padding: 8px 16px; font-weight: bold; border-radius: 4px;")
|
||||
|
||||
cancel_button = QPushButton("取消")
|
||||
cancel_button.setFont(self.normal_font)
|
||||
cancel_button.setStyleSheet("padding: 8px 16px; font-weight: bold; border-radius: 4px;")
|
||||
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(confirm_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 连接按钮信号
|
||||
confirm_button.clicked.connect(dialog.accept)
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
# 显示对话框
|
||||
result = dialog.exec()
|
||||
|
||||
# 如果用户确认,则执行上料操作
|
||||
if result == QDialog.Accepted:
|
||||
selected_type = pallet_combo.currentText()
|
||||
|
||||
# 执行Modbus操作
|
||||
modbus = ModbusUtils()
|
||||
client = modbus.get_client()
|
||||
try:
|
||||
# 上料 D2 寄存器写入 1
|
||||
if modbus.write_register_until_success(client, 2, 1):
|
||||
# 创建状态标签并显示在右上角
|
||||
self.show_operation_status("上料中", "input", selected_type)
|
||||
else:
|
||||
QMessageBox.information(self, "操作提示", "上料失败")
|
||||
except Exception as e:
|
||||
logging.error(f"上料操作失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"上料操作失败: {str(e)}")
|
||||
finally:
|
||||
modbus.close_client(client)
|
||||
|
||||
def handle_output(self):
|
||||
"""处理下料按钮点击事件"""
|
||||
logging.info("下料按钮被点击")
|
||||
QMessageBox.information(self, "操作提示", "开始下料操作")
|
||||
# 这里添加下料相关的业务逻辑
|
||||
# 创建对话框
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("下料操作")
|
||||
dialog.setFixedSize(400, 250)
|
||||
|
||||
# 对话框布局
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
# 添加提示信息
|
||||
info_label = QLabel("请选择下料托盘类型:")
|
||||
info_label.setFont(self.normal_font)
|
||||
layout.addWidget(info_label)
|
||||
|
||||
# 添加托盘类型选择
|
||||
pallet_combo = QComboBox()
|
||||
pallet_combo.setFont(self.normal_font)
|
||||
# 复制当前托盘类型选择器的内容
|
||||
for i in range(self.output_pallet_type_combo.count()):
|
||||
pallet_combo.addItem(self.output_pallet_type_combo.itemText(i))
|
||||
layout.addWidget(pallet_combo)
|
||||
|
||||
# 添加按钮
|
||||
button_layout = QHBoxLayout()
|
||||
confirm_button = QPushButton("确认")
|
||||
confirm_button.setFont(self.normal_font)
|
||||
confirm_button.setStyleSheet("background-color: #fff8e1; border: 1px solid #ffc107; padding: 8px 16px; font-weight: bold; border-radius: 4px;")
|
||||
|
||||
cancel_button = QPushButton("取消")
|
||||
cancel_button.setFont(self.normal_font)
|
||||
cancel_button.setStyleSheet("padding: 8px 16px; font-weight: bold; border-radius: 4px;")
|
||||
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(confirm_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 连接按钮信号
|
||||
confirm_button.clicked.connect(dialog.accept)
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
# 显示对话框
|
||||
result = dialog.exec()
|
||||
|
||||
# 如果用户确认,则执行下料操作
|
||||
if result == QDialog.Accepted:
|
||||
selected_type = pallet_combo.currentText()
|
||||
|
||||
# 执行Modbus操作
|
||||
modbus = ModbusUtils()
|
||||
client = modbus.get_client()
|
||||
try:
|
||||
# 下料 D3 寄存器写入 1
|
||||
if modbus.write_register_until_success(client, 3, 1):
|
||||
# 创建状态标签并显示在右上角
|
||||
self.show_operation_status("下料中", "output", selected_type)
|
||||
else:
|
||||
QMessageBox.information(self, "操作提示", "下料失败")
|
||||
except Exception as e:
|
||||
logging.error(f"下料操作失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"下料操作失败: {str(e)}")
|
||||
finally:
|
||||
modbus.close_client(client)
|
||||
|
||||
def handle_start(self):
|
||||
"""处理开始按钮点击事件"""
|
||||
logging.info("开始按钮被点击")
|
||||
|
||||
# 只有在相机启用时处理相机显示
|
||||
if self.camera_enabled and hasattr(self, 'camera_display'):
|
||||
# 开始显示相机画面
|
||||
if self.camera_display.camera_manager.isOpen:
|
||||
self.camera_display.start_display()
|
||||
|
||||
QMessageBox.information(self, "操作提示", "生产线已启动")
|
||||
# 这里添加启动生产线的业务逻辑
|
||||
"""处理启动按钮点击事件"""
|
||||
modbus = ModbusUtils()
|
||||
client = modbus.get_client()
|
||||
try:
|
||||
# 启动 D1 寄存器写入 1
|
||||
if not modbus.write_register_until_success(client, 1, 1):
|
||||
QMessageBox.information(self, "操作提示", "启动失败")
|
||||
except Exception as e:
|
||||
logging.error(f"启动操作失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"启动操作失败: {str(e)}")
|
||||
finally:
|
||||
modbus.close_client(client)
|
||||
|
||||
def handle_stop(self):
|
||||
"""处理暂停按钮点击事件"""
|
||||
logging.info("暂停按钮被点击")
|
||||
|
||||
# 只有在相机启用时处理相机显示
|
||||
if self.camera_enabled and hasattr(self, 'camera_display'):
|
||||
# 停止显示相机画面
|
||||
self.camera_display.stop_display()
|
||||
|
||||
QMessageBox.information(self, "操作提示", "生产线已暂停")
|
||||
# 这里添加暂停生产线的业务逻辑
|
||||
"""处理停止按钮点击事件"""
|
||||
modbus = ModbusUtils()
|
||||
client = modbus.get_client()
|
||||
try:
|
||||
# 停止 D1 寄存器写入 0
|
||||
if not modbus.write_register_until_success(client, 1, 0):
|
||||
QMessageBox.information(self, "操作提示", "停止失败")
|
||||
except Exception as e:
|
||||
logging.error(f"停止操作失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"停止操作失败: {str(e)}")
|
||||
finally:
|
||||
modbus.close_client(client)
|
||||
|
||||
def handle_camera_status(self, is_connected, message):
|
||||
"""处理相机状态变化"""
|
||||
@ -1052,4 +1231,63 @@ class MainWindow(MainWindowUI):
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"检查数据库记录失败: {str(e)}")
|
||||
QMessageBox.warning(self, "查询失败", f"检查数据库记录失败: {str(e)}")
|
||||
QMessageBox.warning(self, "查询失败", f"检查数据库记录失败: {str(e)}")
|
||||
|
||||
def show_operation_status(self, status, operation_type, pallet_type):
|
||||
"""在右上角显示操作状态
|
||||
|
||||
Args:
|
||||
status: 状态文本
|
||||
operation_type: 操作类型 (input/output)
|
||||
pallet_type: 托盘类型
|
||||
"""
|
||||
# 确定要添加标签的容器
|
||||
if operation_type == "input":
|
||||
container = self.material_content
|
||||
else:
|
||||
container = self.output_content
|
||||
|
||||
# 如果已存在状态标签,则移除它
|
||||
status_label_name = f"{operation_type}_status_label"
|
||||
if hasattr(self, status_label_name):
|
||||
old_label = getattr(self, status_label_name)
|
||||
old_label.deleteLater()
|
||||
|
||||
# 创建新的状态标签
|
||||
status_label = QLabel(f"{status}: {pallet_type}", container)
|
||||
status_label.setFont(self.normal_font)
|
||||
status_label.setStyleSheet("color: red; background-color: transparent;")
|
||||
status_label.setAlignment(Qt.AlignRight | Qt.AlignTop)
|
||||
|
||||
# 使用绝对定位,放置在右上角
|
||||
status_label.setGeometry(container.width() - 200, 5, 190, 30)
|
||||
|
||||
# 确保标签始终保持在顶层显示
|
||||
status_label.raise_()
|
||||
status_label.show()
|
||||
|
||||
# 保存标签引用
|
||||
setattr(self, status_label_name, status_label)
|
||||
|
||||
# 保存原始的resize事件处理函数
|
||||
if not hasattr(container, "_original_resize_event"):
|
||||
container._original_resize_event = container.resizeEvent
|
||||
|
||||
# 添加窗口大小变化事件处理,确保标签位置随窗口调整
|
||||
container.resizeEvent = lambda event: self.adjust_status_label_position(event, container, status_label)
|
||||
|
||||
def adjust_status_label_position(self, event, container, label):
|
||||
"""调整状态标签位置,确保始终在右上角
|
||||
|
||||
Args:
|
||||
event: 窗口大小变化事件
|
||||
container: 标签所在的容器
|
||||
label: 状态标签
|
||||
"""
|
||||
# 更新标签位置,保持在右上角
|
||||
label.setGeometry(container.width() - 200, 5, 190, 30)
|
||||
|
||||
# 调用原始的resizeEvent(如果有的话)
|
||||
original_resize = getattr(container, "_original_resize_event", None)
|
||||
if original_resize:
|
||||
original_resize(event)
|
||||
420
widgets/pallet_type_settings_widget.py
Normal file
420
widgets/pallet_type_settings_widget.py
Normal file
@ -0,0 +1,420 @@
|
||||
import logging
|
||||
from PySide6.QtWidgets import QTableWidget, QTableWidgetItem, QMessageBox, QCheckBox, QPushButton, QLineEdit, QSpinBox
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from ui.pallet_type_settings_ui import PalletTypeSettingsUI
|
||||
from utils.pallet_type_manager import PalletTypeManager
|
||||
|
||||
class PalletTypeSettingsWidget(PalletTypeSettingsUI):
|
||||
"""托盘类型设置部件"""
|
||||
|
||||
# 定义信号
|
||||
signal_pallet_types_changed = Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
|
||||
# 初始化托盘类型管理器
|
||||
self.pallet_type_manager = PalletTypeManager.get_instance()
|
||||
|
||||
# 连接信号和槽
|
||||
self.connect_signals()
|
||||
|
||||
# 加载数据
|
||||
self.load_pallet_types()
|
||||
|
||||
def connect_signals(self):
|
||||
"""连接信号和槽"""
|
||||
# 保存和重置按钮
|
||||
self.save_button.clicked.connect(self.save_all_pallet_types)
|
||||
self.reset_button.clicked.connect(self.load_pallet_types)
|
||||
|
||||
# 上料类型表格和按钮
|
||||
input_table = self.input_widget.findChild(QTableWidget, "input_table")
|
||||
input_table.itemSelectionChanged.connect(lambda: self.handle_table_selection("input"))
|
||||
|
||||
input_add_button = self.input_widget.findChild(QPushButton, "input_add_button")
|
||||
input_add_button.clicked.connect(lambda: self.add_pallet_type("input"))
|
||||
|
||||
input_update_button = self.input_widget.findChild(QPushButton, "input_update_button")
|
||||
input_update_button.clicked.connect(lambda: self.update_pallet_type("input"))
|
||||
|
||||
input_delete_button = self.input_widget.findChild(QPushButton, "input_delete_button")
|
||||
input_delete_button.clicked.connect(lambda: self.delete_pallet_type("input"))
|
||||
|
||||
input_cancel_button = self.input_widget.findChild(QPushButton, "input_cancel_button")
|
||||
input_cancel_button.clicked.connect(lambda: self.cancel_edit("input"))
|
||||
|
||||
# 下料类型表格和按钮
|
||||
output_table = self.output_widget.findChild(QTableWidget, "output_table")
|
||||
output_table.itemSelectionChanged.connect(lambda: self.handle_table_selection("output"))
|
||||
|
||||
output_add_button = self.output_widget.findChild(QPushButton, "output_add_button")
|
||||
output_add_button.clicked.connect(lambda: self.add_pallet_type("output"))
|
||||
|
||||
output_update_button = self.output_widget.findChild(QPushButton, "output_update_button")
|
||||
output_update_button.clicked.connect(lambda: self.update_pallet_type("output"))
|
||||
|
||||
output_delete_button = self.output_widget.findChild(QPushButton, "output_delete_button")
|
||||
output_delete_button.clicked.connect(lambda: self.delete_pallet_type("output"))
|
||||
|
||||
output_cancel_button = self.output_widget.findChild(QPushButton, "output_cancel_button")
|
||||
output_cancel_button.clicked.connect(lambda: self.cancel_edit("output"))
|
||||
|
||||
def load_pallet_types(self):
|
||||
"""加载托盘类型数据"""
|
||||
try:
|
||||
# 重新加载数据
|
||||
self.pallet_type_manager.reload_pallet_types()
|
||||
|
||||
# 加载上料类型
|
||||
self.load_operation_pallet_types("input")
|
||||
|
||||
# 加载下料类型
|
||||
self.load_operation_pallet_types("output")
|
||||
|
||||
logging.info("托盘类型数据已加载")
|
||||
except Exception as e:
|
||||
logging.error(f"加载托盘类型数据失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"加载托盘类型数据失败: {str(e)}")
|
||||
|
||||
def load_operation_pallet_types(self, operation_type):
|
||||
"""加载指定操作类型的托盘类型数据
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
"""
|
||||
# 获取表格
|
||||
table = self.get_table_by_operation_type(operation_type)
|
||||
if not table:
|
||||
return
|
||||
|
||||
# 清空表格
|
||||
table.setRowCount(0)
|
||||
|
||||
# 获取数据
|
||||
pallet_types = self.pallet_type_manager.get_pallet_types_by_operation(operation_type, include_disabled=True)
|
||||
|
||||
# 填充表格
|
||||
for row, pallet_type in enumerate(pallet_types):
|
||||
table.insertRow(row)
|
||||
|
||||
# 类型名称
|
||||
type_name_item = QTableWidgetItem(pallet_type['type_name'])
|
||||
type_name_item.setData(Qt.UserRole, pallet_type['id'])
|
||||
table.setItem(row, 0, type_name_item)
|
||||
|
||||
# 描述
|
||||
desc_item = QTableWidgetItem(pallet_type['description'] or "")
|
||||
table.setItem(row, 1, desc_item)
|
||||
|
||||
# 排序
|
||||
sort_order_item = QTableWidgetItem(str(pallet_type['sort_order']))
|
||||
table.setItem(row, 2, sort_order_item)
|
||||
|
||||
# 启用状态
|
||||
enabled_check = QCheckBox()
|
||||
enabled_check.setChecked(pallet_type['enabled'])
|
||||
enabled_check.stateChanged.connect(lambda state, row=row, id=pallet_type['id']:
|
||||
self.toggle_pallet_type(id, state == Qt.Checked))
|
||||
table.setCellWidget(row, 3, enabled_check)
|
||||
|
||||
# 重置表单
|
||||
self.reset_form(operation_type)
|
||||
|
||||
def get_table_by_operation_type(self, operation_type):
|
||||
"""根据操作类型获取表格
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
|
||||
Returns:
|
||||
QTableWidget: 表格部件
|
||||
"""
|
||||
if operation_type == "input":
|
||||
return self.input_widget.findChild(QTableWidget, "input_table")
|
||||
elif operation_type == "output":
|
||||
return self.output_widget.findChild(QTableWidget, "output_table")
|
||||
return None
|
||||
|
||||
def get_form_values(self, operation_type):
|
||||
"""获取表单值
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
|
||||
Returns:
|
||||
dict: 表单值
|
||||
"""
|
||||
widget = self.input_widget if operation_type == "input" else self.output_widget
|
||||
|
||||
type_name_input = widget.findChild(QLineEdit, f"{operation_type}_type_name_input")
|
||||
desc_input = widget.findChild(QLineEdit, f"{operation_type}_desc_input")
|
||||
sort_order_spin = widget.findChild(QSpinBox, f"{operation_type}_sort_order_spin")
|
||||
enabled_check = widget.findChild(QCheckBox, f"{operation_type}_enabled_check")
|
||||
|
||||
return {
|
||||
'type_name': type_name_input.text().strip(),
|
||||
'operation_type': operation_type,
|
||||
'description': desc_input.text().strip(),
|
||||
'sort_order': sort_order_spin.value(),
|
||||
'enabled': enabled_check.isChecked()
|
||||
}
|
||||
|
||||
def set_form_values(self, operation_type, values):
|
||||
"""设置表单值
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
values: 表单值
|
||||
"""
|
||||
widget = self.input_widget if operation_type == "input" else self.output_widget
|
||||
|
||||
type_name_input = widget.findChild(QLineEdit, f"{operation_type}_type_name_input")
|
||||
desc_input = widget.findChild(QLineEdit, f"{operation_type}_desc_input")
|
||||
sort_order_spin = widget.findChild(QSpinBox, f"{operation_type}_sort_order_spin")
|
||||
enabled_check = widget.findChild(QCheckBox, f"{operation_type}_enabled_check")
|
||||
|
||||
type_name_input.setText(values.get('type_name', ''))
|
||||
desc_input.setText(values.get('description', ''))
|
||||
sort_order_spin.setValue(values.get('sort_order', 100))
|
||||
enabled_check.setChecked(values.get('enabled', True))
|
||||
|
||||
def reset_form(self, operation_type):
|
||||
"""重置表单
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
"""
|
||||
widget = self.input_widget if operation_type == "input" else self.output_widget
|
||||
|
||||
# 重置表单值
|
||||
self.set_form_values(operation_type, {
|
||||
'type_name': '',
|
||||
'description': '',
|
||||
'sort_order': 100,
|
||||
'enabled': True
|
||||
})
|
||||
|
||||
# 重置当前编辑ID
|
||||
widget.setProperty("current_edit_id", -1)
|
||||
|
||||
# 重置按钮状态
|
||||
add_button = widget.findChild(QPushButton, f"{operation_type}_add_button")
|
||||
update_button = widget.findChild(QPushButton, f"{operation_type}_update_button")
|
||||
delete_button = widget.findChild(QPushButton, f"{operation_type}_delete_button")
|
||||
cancel_button = widget.findChild(QPushButton, f"{operation_type}_cancel_button")
|
||||
|
||||
add_button.setEnabled(True)
|
||||
update_button.setEnabled(False)
|
||||
delete_button.setEnabled(False)
|
||||
cancel_button.setEnabled(False)
|
||||
|
||||
def handle_table_selection(self, operation_type):
|
||||
"""处理表格选择事件
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
"""
|
||||
table = self.get_table_by_operation_type(operation_type)
|
||||
if not table:
|
||||
return
|
||||
|
||||
# 获取选中行
|
||||
selected_rows = table.selectionModel().selectedRows()
|
||||
if not selected_rows:
|
||||
return
|
||||
|
||||
# 获取选中行数据
|
||||
row = selected_rows[0].row()
|
||||
pallet_type_id = table.item(row, 0).data(Qt.UserRole)
|
||||
|
||||
# 获取托盘类型数据
|
||||
pallet_type = self.pallet_type_manager.get_pallet_type_by_id(pallet_type_id)
|
||||
if not pallet_type:
|
||||
return
|
||||
|
||||
# 设置表单值
|
||||
self.set_form_values(operation_type, pallet_type)
|
||||
|
||||
# 设置当前编辑ID
|
||||
widget = self.input_widget if operation_type == "input" else self.output_widget
|
||||
widget.setProperty("current_edit_id", pallet_type_id)
|
||||
|
||||
# 设置按钮状态
|
||||
add_button = widget.findChild(QPushButton, f"{operation_type}_add_button")
|
||||
update_button = widget.findChild(QPushButton, f"{operation_type}_update_button")
|
||||
delete_button = widget.findChild(QPushButton, f"{operation_type}_delete_button")
|
||||
cancel_button = widget.findChild(QPushButton, f"{operation_type}_cancel_button")
|
||||
|
||||
add_button.setEnabled(False)
|
||||
update_button.setEnabled(True)
|
||||
delete_button.setEnabled(True)
|
||||
cancel_button.setEnabled(True)
|
||||
|
||||
def validate_form(self, operation_type):
|
||||
"""验证表单
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
|
||||
Returns:
|
||||
bool: 表单是否有效
|
||||
"""
|
||||
values = self.get_form_values(operation_type)
|
||||
|
||||
if not values['type_name']:
|
||||
QMessageBox.warning(self, "警告", "托盘类型名称不能为空")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def add_pallet_type(self, operation_type):
|
||||
"""添加托盘类型
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
"""
|
||||
if not self.validate_form(operation_type):
|
||||
return
|
||||
|
||||
try:
|
||||
# 获取表单值
|
||||
values = self.get_form_values(operation_type)
|
||||
|
||||
# 创建托盘类型
|
||||
result = self.pallet_type_manager.create_pallet_type(values)
|
||||
if result:
|
||||
logging.info(f"添加托盘类型成功: {values['type_name']}")
|
||||
|
||||
# 重新加载数据
|
||||
self.load_operation_pallet_types(operation_type)
|
||||
|
||||
# 发送信号
|
||||
self.signal_pallet_types_changed.emit()
|
||||
else:
|
||||
QMessageBox.critical(self, "错误", "添加托盘类型失败")
|
||||
except Exception as e:
|
||||
logging.error(f"添加托盘类型失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"添加托盘类型失败: {str(e)}")
|
||||
|
||||
def update_pallet_type(self, operation_type):
|
||||
"""更新托盘类型
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
"""
|
||||
if not self.validate_form(operation_type):
|
||||
return
|
||||
|
||||
try:
|
||||
# 获取当前编辑ID
|
||||
widget = self.input_widget if operation_type == "input" else self.output_widget
|
||||
pallet_type_id = widget.property("current_edit_id")
|
||||
if pallet_type_id < 0:
|
||||
QMessageBox.warning(self, "警告", "请先选择要编辑的托盘类型")
|
||||
return
|
||||
|
||||
# 获取表单值
|
||||
values = self.get_form_values(operation_type)
|
||||
|
||||
# 更新托盘类型
|
||||
result = self.pallet_type_manager.update_pallet_type(pallet_type_id, values)
|
||||
if result:
|
||||
logging.info(f"更新托盘类型成功: {values['type_name']}")
|
||||
|
||||
# 重新加载数据
|
||||
self.load_operation_pallet_types(operation_type)
|
||||
|
||||
# 发送信号
|
||||
self.signal_pallet_types_changed.emit()
|
||||
else:
|
||||
QMessageBox.critical(self, "错误", "更新托盘类型失败")
|
||||
except Exception as e:
|
||||
logging.error(f"更新托盘类型失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"更新托盘类型失败: {str(e)}")
|
||||
|
||||
def delete_pallet_type(self, operation_type):
|
||||
"""删除托盘类型
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
"""
|
||||
try:
|
||||
# 获取当前编辑ID
|
||||
widget = self.input_widget if operation_type == "input" else self.output_widget
|
||||
pallet_type_id = widget.property("current_edit_id")
|
||||
if pallet_type_id < 0:
|
||||
QMessageBox.warning(self, "警告", "请先选择要删除的托盘类型")
|
||||
return
|
||||
|
||||
# 确认删除
|
||||
pallet_type = self.pallet_type_manager.get_pallet_type_by_id(pallet_type_id)
|
||||
if not pallet_type:
|
||||
QMessageBox.warning(self, "警告", "找不到要删除的托盘类型")
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(self, "确认删除",
|
||||
f"确定要删除托盘类型 '{pallet_type['type_name']}' 吗?",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
# 删除托盘类型
|
||||
result = self.pallet_type_manager.delete_pallet_type(pallet_type_id)
|
||||
if result:
|
||||
logging.info(f"删除托盘类型成功: {pallet_type['type_name']}")
|
||||
|
||||
# 重新加载数据
|
||||
self.load_operation_pallet_types(operation_type)
|
||||
|
||||
# 发送信号
|
||||
self.signal_pallet_types_changed.emit()
|
||||
else:
|
||||
QMessageBox.critical(self, "错误", "删除托盘类型失败")
|
||||
except Exception as e:
|
||||
logging.error(f"删除托盘类型失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"删除托盘类型失败: {str(e)}")
|
||||
|
||||
def cancel_edit(self, operation_type):
|
||||
"""取消编辑
|
||||
|
||||
Args:
|
||||
operation_type: 操作类型 (input/output)
|
||||
"""
|
||||
# 重置表单
|
||||
self.reset_form(operation_type)
|
||||
|
||||
# 取消表格选择
|
||||
table = self.get_table_by_operation_type(operation_type)
|
||||
if table:
|
||||
table.clearSelection()
|
||||
|
||||
def toggle_pallet_type(self, pallet_type_id, enabled):
|
||||
"""启用或禁用托盘类型
|
||||
|
||||
Args:
|
||||
pallet_type_id: 托盘类型ID
|
||||
enabled: 是否启用
|
||||
"""
|
||||
try:
|
||||
# 更新启用状态
|
||||
result = self.pallet_type_manager.toggle_pallet_type(pallet_type_id, enabled)
|
||||
if result:
|
||||
logging.info(f"更新托盘类型启用状态成功: {pallet_type_id} -> {enabled}")
|
||||
|
||||
# 发送信号
|
||||
self.signal_pallet_types_changed.emit()
|
||||
else:
|
||||
QMessageBox.critical(self, "错误", "更新托盘类型启用状态失败")
|
||||
except Exception as e:
|
||||
logging.error(f"更新托盘类型启用状态失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"更新托盘类型启用状态失败: {str(e)}")
|
||||
|
||||
def save_all_pallet_types(self):
|
||||
"""保存所有托盘类型"""
|
||||
# 目前没有需要批量保存的操作,所有操作都是实时保存的
|
||||
QMessageBox.information(self, "提示", "托盘类型配置已保存")
|
||||
|
||||
# 发送信号
|
||||
self.signal_pallet_types_changed.emit()
|
||||
@ -3,6 +3,7 @@ import logging
|
||||
from ui.settings_ui import SettingsUI
|
||||
from utils.sql_utils import SQLUtils
|
||||
from widgets.inspection_settings_widget import InspectionSettingsWidget
|
||||
from widgets.pallet_type_settings_widget import PalletTypeSettingsWidget
|
||||
|
||||
class SettingsWidget(SettingsUI):
|
||||
def __init__(self, parent=None):
|
||||
@ -31,6 +32,26 @@ class SettingsWidget(SettingsUI):
|
||||
else:
|
||||
logging.error("无法找到inspection_layout布局")
|
||||
|
||||
# 创建托盘类型设置部件
|
||||
logging.info("创建PalletTypeSettingsWidget实例")
|
||||
self.pallet_type_settings = PalletTypeSettingsWidget()
|
||||
|
||||
# 移除临时占位符标签并添加托盘类型设置部件
|
||||
if hasattr(self, 'pallet_type_placeholder'):
|
||||
logging.info("移除托盘类型临时占位符")
|
||||
self.pallet_type_layout.removeWidget(self.pallet_type_placeholder)
|
||||
self.pallet_type_placeholder.hide()
|
||||
self.pallet_type_placeholder.deleteLater()
|
||||
else:
|
||||
logging.warning("未找到托盘类型临时占位符标签")
|
||||
|
||||
# 检查布局是否可用
|
||||
if hasattr(self, 'pallet_type_layout'):
|
||||
logging.info("添加托盘类型设置部件到布局")
|
||||
self.pallet_type_layout.addWidget(self.pallet_type_settings)
|
||||
else:
|
||||
logging.error("无法找到pallet_type_layout布局")
|
||||
|
||||
# 连接信号和槽
|
||||
self.connect_signals()
|
||||
|
||||
@ -51,6 +72,13 @@ class SettingsWidget(SettingsUI):
|
||||
|
||||
# 检验配置变更信号
|
||||
self.inspection_settings.signal_configs_changed.connect(self.handle_inspection_configs_changed)
|
||||
|
||||
# 托盘类型配置变更信号
|
||||
self.pallet_type_settings.signal_pallet_types_changed.connect(self.handle_pallet_types_changed)
|
||||
|
||||
# 返回按钮
|
||||
if hasattr(self, 'back_button'):
|
||||
self.back_button.clicked.connect(self.back_to_main)
|
||||
|
||||
def update_db_ui_state(self):
|
||||
"""根据选择的数据库类型更新UI状态"""
|
||||
@ -165,4 +193,16 @@ class SettingsWidget(SettingsUI):
|
||||
logging.info("检验配置已更新")
|
||||
# 如果有父窗口,通知父窗口更新检验配置
|
||||
if self.parent and hasattr(self.parent, 'update_inspection_columns'):
|
||||
self.parent.update_inspection_columns()
|
||||
self.parent.update_inspection_columns()
|
||||
|
||||
def handle_pallet_types_changed(self):
|
||||
"""处理托盘类型配置变更"""
|
||||
logging.info("托盘类型配置已更新")
|
||||
# 如果有父窗口,通知父窗口更新托盘类型
|
||||
if self.parent and hasattr(self.parent, 'update_pallet_types'):
|
||||
self.parent.update_pallet_types()
|
||||
|
||||
def back_to_main(self):
|
||||
"""返回主页"""
|
||||
if self.parent and hasattr(self.parent, 'show_main_page'):
|
||||
self.parent.show_main_page()
|
||||
Loading…
Reference in New Issue
Block a user