feat: 完成扫码操作

This commit is contained in:
zhu-mengmeng 2025-07-01 09:36:16 +08:00
parent ead4cfcc95
commit ce8483d298
7 changed files with 3354 additions and 194 deletions

View File

@ -49,8 +49,8 @@
"default_framerate": 30 "default_framerate": 30
}, },
"modbus": { "modbus": {
"host": "192.168.2.88", "host": "localhost",
"port": "502" "port": "5020"
}, },
"serial": { "serial": {
"keyboard": { "keyboard": {

View File

@ -8,12 +8,13 @@ class InspectionDAO:
def __init__(self): def __init__(self):
"""初始化数据访问对象""" """初始化数据访问对象"""
self.db = SQLUtils('sqlite', database='db/jtDB.db') # 不再在初始化时创建数据库连接,而是在需要时创建
pass
def __del__(self): def __del__(self):
"""析构函数,确保数据库连接关闭""" """析构函数,确保数据库连接关闭"""
if hasattr(self, 'db'): # 不再需要在这里关闭连接,由上下文管理器处理
self.db.close() pass
def get_all_inspection_configs(self, include_disabled=False): def get_all_inspection_configs(self, include_disabled=False):
"""获取所有检验项目配置 """获取所有检验项目配置
@ -44,8 +45,9 @@ class InspectionDAO:
""" """
params = () params = ()
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
results = self.db.cursor.fetchall() db.cursor.execute(sql, params)
results = db.cursor.fetchall()
configs = [] configs = []
for row in results: for row in results:
@ -96,8 +98,9 @@ class InspectionDAO:
""" """
params = (position,) params = (position,)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
row = self.db.cursor.fetchone() db.cursor.execute(sql, params)
row = db.cursor.fetchone()
if row: if row:
config = { config = {
@ -182,7 +185,8 @@ class InspectionDAO:
WHERE id = ? WHERE id = ?
""" """
self.db.execute_update(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.execute_update(sql, params)
return True return True
except Exception as e: except Exception as e:
logging.error(f"更新检验项目配置失败: {str(e)}") logging.error(f"更新检验项目配置失败: {str(e)}")
@ -209,7 +213,8 @@ class InspectionDAO:
""" """
params = (enabled, current_time, username, position) params = (enabled, current_time, username, position)
self.db.execute_update(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.execute_update(sql, params)
return True return True
except Exception as e: except Exception as e:
logging.error(f"更新检验项目启用状态失败: {str(e)}") logging.error(f"更新检验项目启用状态失败: {str(e)}")
@ -229,8 +234,9 @@ class InspectionDAO:
# 先检查是否存在记录 # 先检查是否存在记录
check_sql = "SELECT ddmo FROM wsbz_order_info WHERE ddmo = ?" check_sql = "SELECT ddmo FROM wsbz_order_info WHERE ddmo = ?"
self.db.cursor.execute(check_sql, (data.get("mo", ""),)) with SQLUtils('sqlite', database='db/jtDB.db') as db:
existing_record = self.db.cursor.fetchone() db.cursor.execute(check_sql, (data.get("mo", ""),))
existing_record = db.cursor.fetchone()
if existing_record: if existing_record:
# 如果记录存在,执行更新 # 如果记录存在,执行更新
@ -329,102 +335,81 @@ class InspectionDAO:
) )
logging.info(f"插入新订单信息: ddmo={data.get('mo', '')}") logging.info(f"插入新订单信息: ddmo={data.get('mo', '')}")
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
self.db.conn.commit() db.cursor.execute(sql, params)
db.conn.commit()
return True return True
except Exception as e: except Exception as e:
logging.error(f"保存订单信息失败: {str(e)}") logging.error(f"保存订单信息失败: {str(e)}")
self.db.conn.rollback() with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.conn.rollback()
return False return False
def save_inspection_data(self, order_id,gc_note, data, username='system'): def save_inspection_data(self, order_id, gc_note, data, username='system'):
"""保存检验数据 """保存检验数据
Args: Args:
order_id: 工程 order_id: 订单
gc_note: 工程号备注 gc_note: 工程号
data: 检验数据列表格式: [{'position': 1, 'config_id': 1, 'value': '合格'}, ...] data: 检验数据列表每项包含position, config_id, value, status, remark
username: 操作用户 username: 操作用户
Returns: Returns:
bool: 保存是否成功 bool: 保存是否成功
""" """
try: try:
# 验证数据格式
if not isinstance(data, list):
logging.error(f"保存检验数据失败: data参数必须是列表实际类型为 {type(data)}")
return False
if len(data) == 0:
logging.warning("保存检验数据: 数据列表为空")
return True # 空列表视为成功,不进行任何操作
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.db.begin_transaction() # 使用上下文管理器自动处理连接和游标
with SQLUtils('sqlite', database='db/jtDB.db') as db:
for item in data: for item in data:
# 验证item是否为字典类型 position = item.get('position')
if not isinstance(item, dict): config_id = item.get('config_id')
logging.error(f"保存检验数据失败: 数据项必须是字典,实际类型为 {type(item)}") value = item.get('value', '')
self.db.rollback_transaction() status = item.get('status', '')
return False remark = item.get('remark', '')
tray_id = item.get('tray_id', '')
# 使用get方法安全获取值提供默认值 # 检查是否已存在记录
position = item.get('position') check_sql = """
config_id = item.get('config_id') SELECT id FROM wsbz_inspection_data
value = item.get('value', '') WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ?
status = item.get('status', 'pass')
remark = item.get('remark', '')
tray_id = item.get('tray_id', '')
# 验证必要字段
if position is None or config_id is None:
logging.error(f"保存检验数据失败: 缺少必要字段 position 或 config_id")
self.db.rollback_transaction()
return False
# 检查是否已存在该工程号和位置的记录
check_sql = """
SELECT id FROM wsbz_inspection_data
WHERE order_id = ? and gc_note = ? AND position = ? AND is_deleted = FALSE
"""
check_params = (order_id,gc_note, position)
self.db.cursor.execute(check_sql, check_params)
existing = self.db.cursor.fetchone()
if existing:
# 更新已有记录
update_sql = """
UPDATE wsbz_inspection_data
SET config_id = ?, value = ?, status = ?, remark = ?,
update_time = ?, update_by = ?, tray_id = ?
WHERE id = ?
""" """
update_params = ( check_params = (order_id, gc_note, position, tray_id)
config_id, value, status, remark,
current_time, username, tray_id, existing[0] db.execute_query(check_sql, check_params)
) existing_record = db.fetchone()
self.db.cursor.execute(update_sql, update_params)
else: if existing_record:
# 插入新记录 # 更新现有记录
insert_sql = """ update_sql = """
INSERT INTO wsbz_inspection_data ( UPDATE wsbz_inspection_data
order_id, position, config_id, value, status, remark, SET config_id = ?, value = ?, status = ?, remark = ?,
create_time, create_by, is_deleted, tray_id,gc_note update_time = ?, update_by = ?
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, FALSE, ?, ?) WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ?
""" """
insert_params = ( update_params = (
order_id, position, config_id, value, status, remark, config_id, value, status, remark,
current_time, username, tray_id,gc_note current_time, username,
) order_id, gc_note, position, tray_id
self.db.cursor.execute(insert_sql, insert_params) )
db.execute_update(update_sql, update_params)
else:
# 插入新记录
insert_sql = """
INSERT INTO wsbz_inspection_data (
order_id, gc_note, position, config_id, value, status, remark,
create_time, create_by, update_time, update_by, tray_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
insert_params = (
order_id, gc_note, position, config_id, value, status, remark,
current_time, username, current_time, username, tray_id
)
db.execute_update(insert_sql, insert_params)
self.db.commit_transaction()
return True return True
except Exception as e: except Exception as e:
self.db.rollback_transaction()
logging.error(f"保存检验数据失败: {str(e)}") logging.error(f"保存检验数据失败: {str(e)}")
return False return False
def get_inspection_data_unfinished(self, tray_id): def get_inspection_data_unfinished(self, tray_id):
@ -442,8 +427,9 @@ class InspectionDAO:
AND d.position = 11 AND COALESCE(d.value,'') = '' AND d.position = 11 AND COALESCE(d.value,'') = ''
""" """
params = (tray_id,) params = (tray_id,)
self.db.cursor.execute(sql_orders, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
gc_notes = self.db.cursor.fetchall() db.cursor.execute(sql_orders, params)
gc_notes = db.cursor.fetchall()
if not gc_notes: if not gc_notes:
return [] return []
@ -464,8 +450,9 @@ class InspectionDAO:
""" """
params = [tray_id] + gc_notes params = [tray_id] + gc_notes
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
results = self.db.cursor.fetchall() db.cursor.execute(sql, params)
results = db.cursor.fetchall()
data_list = [] data_list = []
for row in results: for row in results:
@ -509,8 +496,9 @@ class InspectionDAO:
""" """
params = (order_id, gc_note, tray_id) params = (order_id, gc_note, tray_id)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
results = self.db.cursor.fetchall() db.cursor.execute(sql, params)
results = db.cursor.fetchall()
data_list = [] data_list = []
for row in results: for row in results:
@ -559,8 +547,9 @@ class InspectionDAO:
ORDER BY pack_time DESC ORDER BY pack_time DESC
""" """
params = (tray_id,) params = (tray_id,)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
results = self.db.cursor.fetchall() db.cursor.execute(sql, params)
results = db.cursor.fetchall()
return results return results
except Exception as e: except Exception as e:
logging.error(f"获取包装记录失败: {str(e)}") logging.error(f"获取包装记录失败: {str(e)}")
@ -582,11 +571,13 @@ class InspectionDAO:
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
""" """
params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False,gc_note) params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False,gc_note)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
self.db.conn.commit() db.cursor.execute(sql, params)
db.conn.commit()
except Exception as e: except Exception as e:
logging.error(f"保存包装记录失败: {str(e)}") logging.error(f"保存包装记录失败: {str(e)}")
self.db.conn.rollback() with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.conn.rollback()
def delete_inspection_data(self, order_id, tray_id): def delete_inspection_data(self, order_id, tray_id):
"""删除检验数据 """删除检验数据
@ -602,11 +593,13 @@ class InspectionDAO:
WHERE order_id = ? AND tray_id = ? WHERE order_id = ? AND tray_id = ?
""" """
params = (order_id, tray_id) params = (order_id, tray_id)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
self.db.conn.commit() db.cursor.execute(sql, params)
db.conn.commit()
except Exception as e: except Exception as e:
logging.error(f"删除检验数据失败: {str(e)}") logging.error(f"删除检验数据失败: {str(e)}")
self.db.conn.rollback() with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.conn.rollback()
def get_axios_num_by_order_id(self, order_id): def get_axios_num_by_order_id(self, order_id):
"""获取托盘号对应的轴号""" """获取托盘号对应的轴号"""
try: try:
@ -615,8 +608,9 @@ class InspectionDAO:
AND is_deleted = FALSE AND is_deleted = FALSE
""" """
params = (order_id,) params = (order_id,)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
result = self.db.cursor.fetchone() db.cursor.execute(sql, params)
result = db.cursor.fetchone()
return int(result[0]) if result[0] else 0 return int(result[0]) if result[0] else 0
except Exception as e: except Exception as e:
logging.error(f"获取轴号失败: {str(e)}") logging.error(f"获取轴号失败: {str(e)}")
@ -629,8 +623,9 @@ class InspectionDAO:
AND is_deleted = FALSE AND is_deleted = FALSE
""" """
params = (tray_id,) params = (tray_id,)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
result = self.db.cursor.fetchone() db.cursor.execute(sql, params)
result = db.cursor.fetchone()
return int(result[0]) if result else 0 return int(result[0]) if result else 0
except Exception as e: except Exception as e:
logging.error(f"获取轴号失败: {str(e)}") logging.error(f"获取轴号失败: {str(e)}")
@ -642,8 +637,9 @@ class InspectionDAO:
SELECT gzl_zl FROM wsbz_order_info WHERE ddmo = ? SELECT gzl_zl FROM wsbz_order_info WHERE ddmo = ?
""" """
params = (order_id,) params = (order_id,)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
result = self.db.cursor.fetchone() db.cursor.execute(sql, params)
result = db.cursor.fetchone()
return result[0] if result else 0 return result[0] if result else 0
except Exception as e: except Exception as e:
logging.error(f"获取工字轮重量失败: {str(e)}") logging.error(f"获取工字轮重量失败: {str(e)}")
@ -655,8 +651,9 @@ class InspectionDAO:
SELECT bccd, tccd FROM wsbz_order_info WHERE ddmo = ? SELECT bccd, tccd FROM wsbz_order_info WHERE ddmo = ?
""" """
params = (order_id,) params = (order_id,)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
result = self.db.cursor.fetchone() db.cursor.execute(sql, params)
result = db.cursor.fetchone()
return result[0],result[1] if result else None,None return result[0],result[1] if result else None,None
except Exception as e: except Exception as e:
logging.error(f"获取线径范围失败: {str(e)}") logging.error(f"获取线径范围失败: {str(e)}")
@ -675,8 +672,9 @@ class InspectionDAO:
SELECT MIN(create_time) FROM wsbz_inspection_data SELECT MIN(create_time) FROM wsbz_inspection_data
WHERE order_id = ? AND is_deleted = FALSE WHERE order_id = ? AND is_deleted = FALSE
""" """
self.db.cursor.execute(sql, (order_id,)) with SQLUtils('sqlite', database='db/jtDB.db') as db:
result = self.db.cursor.fetchone() db.cursor.execute(sql, (order_id,))
result = db.cursor.fetchone()
return result[0] if result and result[0] else None return result[0] if result and result[0] else None
except Exception as e: except Exception as e:
logging.error(f"获取工程号创建时间失败: {str(e)}") logging.error(f"获取工程号创建时间失败: {str(e)}")
@ -707,8 +705,9 @@ class InspectionDAO:
ORDER BY first_create_time ORDER BY first_create_time
""" """
self.db.cursor.execute(sql, order_ids) with SQLUtils('sqlite', database='db/jtDB.db') as db:
results = self.db.cursor.fetchall() db.cursor.execute(sql, order_ids)
results = db.cursor.fetchall()
# 提取排序后的工程号 # 提取排序后的工程号
sorted_order_ids = [row[0] for row in results] sorted_order_ids = [row[0] for row in results]
@ -739,14 +738,15 @@ class InspectionDAO:
FROM wsbz_order_info WHERE ddmo = ? FROM wsbz_order_info WHERE ddmo = ?
""" """
params = (order_id,) params = (order_id,)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
result = self.db.cursor.fetchone() db.cursor.execute(sql, params)
result = db.cursor.fetchone()
if not result: if not result:
return {} return {}
# 获取列名 # 获取列名
column_names = [desc[0] for desc in self.db.cursor.description] column_names = [desc[0] for desc in db.cursor.description]
# 转换为字典 # 转换为字典
result_dict = {} result_dict = {}
@ -779,8 +779,9 @@ class InspectionDAO:
AND COALESCE(value, '') != '' AND COALESCE(value, '') != ''
""" """
params = (gc_note, order_id, tray_id) params = (gc_note, order_id, tray_id)
self.db.cursor.execute(sql, params) with SQLUtils('sqlite', database='db/jtDB.db') as db:
results = self.db.cursor.fetchall() db.cursor.execute(sql, params)
results = db.cursor.fetchall()
if not results: if not results:
return {} return {}

Binary file not shown.

2973
tests/main_window_old.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@ class SerialManager:
self.read_threads: Dict[str, threading.Thread] = {} # 存储读取线程 self.read_threads: Dict[str, threading.Thread] = {} # 存储读取线程
self.running_flags: Dict[str, bool] = {} # 存储线程运行标志 self.running_flags: Dict[str, bool] = {} # 存储线程运行标志
self.callbacks: Dict[str, Callable] = {} # 存储数据回调函数 self.callbacks: Dict[str, Callable] = {} # 存储数据回调函数
self.port_types: Dict[str, str] = {} # 存储端口类型,用于线程重启
# 添加文件操作暂停控制 # 添加文件操作暂停控制
self._file_operations_suspended = False self._file_operations_suspended = False
@ -85,6 +86,9 @@ class SerialManager:
else: else:
logging.info("键盘监听功能已在配置中禁用,跳过初始化键盘监听器") logging.info("键盘监听功能已在配置中禁用,跳过初始化键盘监听器")
self.keyboard_listener = None self.keyboard_listener = None
# 启动线程监控
self._start_thread_monitor()
def _load_config(self): def _load_config(self):
"""加载配置""" """加载配置"""
@ -257,31 +261,58 @@ class SerialManager:
if callback: if callback:
self.callbacks[port_name] = callback self.callbacks[port_name] = callback
# 记录端口类型,用于线程重启
self.port_types[port_name] = port_type
# 创建并启动读取线程 # 创建并启动读取线程
self.running_flags[port_name] = True self.running_flags[port_name] = True
# 统一线程创建和管理方式
if port_type == 'cz': if port_type == 'cz':
# 称重数据需要特殊处理 # 称重数据需要特殊处理
thread = threading.Thread(target=self._read_weight_thread, args=(port_name, self.stable_threshold)) thread = threading.Thread(
target=self._read_weight_thread,
args=(port_name, self.stable_threshold),
daemon=True,
name=f"Thread-{port_type}-{port_name}"
)
elif port_type == 'mdz': elif port_type == 'mdz':
# 米电阻数据需要特殊处理 # 米电阻数据需要特殊处理
thread = threading.Thread(target=self._read_resistance_thread, args=(port_name,)) thread = threading.Thread(
target=self._read_resistance_thread,
args=(port_name,),
daemon=True,
name=f"Thread-{port_type}-{port_name}"
)
elif port_type == 'xj': elif port_type == 'xj':
# 线径数据需要特殊处理 # 线径数据需要特殊处理
thread = threading.Thread(target=self._read_diameter_thread, args=(port_name,)) thread = threading.Thread(
thread.daemon = True target=self._read_diameter_thread,
thread.start() args=(port_name,),
self.read_threads[port_name] = thread daemon=True,
name=f"Thread-{port_type}-{port_name}"
)
elif port_type == 'scanner': elif port_type == 'scanner':
# 扫码器数据需要特殊处理 # 扫码器数据需要特殊处理
thread = threading.Thread(target=self._read_scanner_thread, args=(port_name,)) thread = threading.Thread(
target=self._read_scanner_thread,
args=(port_name,),
daemon=True,
name=f"Thread-{port_type}-{port_name}"
)
else: else:
# 其他类型使用通用处理 # 其他类型使用通用处理
thread = threading.Thread(target=self._read_thread, args=(port_name,)) thread = threading.Thread(
target=self._read_thread,
thread.daemon = True args=(port_name,),
thread.start() daemon=True,
self.read_threads[port_name] = thread name=f"Thread-{port_type}-{port_name}"
)
# 统一启动线程
thread.start()
self.read_threads[port_name] = thread
logging.info(f"已启动串口读取线程: {thread.name}")
return True return True
@ -330,10 +361,13 @@ class SerialManager:
del self.serial_ports[port_name] del self.serial_ports[port_name]
logging.info(f"串行对象 for {port_name} 从 self.serial_ports 中删除. 当前活跃端口: {list(self.serial_ports.keys())}") logging.info(f"串行对象 for {port_name} 从 self.serial_ports 中删除. 当前活跃端口: {list(self.serial_ports.keys())}")
# 删除回调 # 删除回调和端口类型记录
if port_name in self.callbacks: if port_name in self.callbacks:
del self.callbacks[port_name] del self.callbacks[port_name]
if port_name in self.port_types:
del self.port_types[port_name]
logging.info(f"串口 {port_name} 已关闭") logging.info(f"串口 {port_name} 已关闭")
return True return True
@ -434,16 +468,46 @@ class SerialManager:
logging.error(f"串口 {port_name} 读取线程异常: {str(e)}") logging.error(f"串口 {port_name} 读取线程异常: {str(e)}")
def _read_weight_thread(self, port_name: str, stable_threshold: int = 10): def _read_weight_thread(self, port_name: str, stable_threshold: int = 10):
logging.info(f"[{port_name}] 称重线程启动") """
# 重置状态变量,确保线程重启时能正确处理称重数据 称重串口读取线程
self.weight_written = False
self.stable_count = 0
self.last_weight = 0
self.last_weights = [0] * 3
self.weight_changed_time = time.time()
self.last_write_time = 0 # 添加一个变量来跟踪最后一次写入时间
self.stability_start_time = 0 # 重置稳定性检测开始时间
Args:
port_name: 串口名称
stable_threshold: 稳定阈值
"""
try:
logging.info(f"[{port_name}] 称重线程启动")
# 重置状态变量,确保线程重启时能正确处理称重数据
self.weight_written = False
self.stable_count = 0
self.last_weight = 0
self.last_weights = [0] * 3
self.weight_changed_time = time.time()
self.last_write_time = 0 # 添加一个变量来跟踪最后一次写入时间
self.stability_start_time = 0 # 重置稳定性检测开始时间
# 添加实际的读取逻辑
while self.running_flags.get(port_name, False):
if not self.is_port_open(port_name):
time.sleep(0.1)
continue
# 检查是否有数据可读
if self.serial_ports[port_name].in_waiting > 0:
data = self.serial_ports[port_name].read(self.serial_ports[port_name].in_waiting)
weight = self._process_weight_data(data)
if weight is not None:
# 更新数据
self.data['cz'] = weight
self._write_data_to_file()
time.sleep(0.1)
except Exception as e:
logging.error(f"称重串口 {port_name} 读取线程异常: {e}")
# 线程异常时,尝试重置状态
self.running_flags[port_name] = False
def _process_weight_data(self, data_bytes): def _process_weight_data(self, data_bytes):
""" """
TODO: 需要将线径数据写入文件这个方法需要修改成线径的串口数据获取 TODO: 需要将线径数据写入文件这个方法需要修改成线径的串口数据获取
@ -496,6 +560,7 @@ class SerialManager:
port_name: 串口名称 port_name: 串口名称
""" """
try: try:
logging.info(f"[{port_name}] 米电阻线程启动")
while self.running_flags.get(port_name, False): while self.running_flags.get(port_name, False):
if not self.is_port_open(port_name): if not self.is_port_open(port_name):
time.sleep(0.1) time.sleep(0.1)
@ -536,7 +601,9 @@ class SerialManager:
except Exception as e: except Exception as e:
logging.error(f"米电阻串口 {port_name} 读取线程异常: {e}") logging.error(f"米电阻串口 {port_name} 读取线程异常: {e}")
# 线程异常时,尝试重置状态
self.running_flags[port_name] = False
def _process_mdz_response(self, port_name, response_bytes: bytes): def _process_mdz_response(self, port_name, response_bytes: bytes):
"""处理米电阻响应数据""" """处理米电阻响应数据"""
try: try:
@ -1075,6 +1142,7 @@ class SerialManager:
port_name: 串口名称 port_name: 串口名称
""" """
try: try:
logging.info(f"[{port_name}] 线径线程启动")
while self.running_flags.get(port_name, False): while self.running_flags.get(port_name, False):
if not self.is_port_open(port_name): if not self.is_port_open(port_name):
time.sleep(0.1) time.sleep(0.1)
@ -1089,6 +1157,8 @@ class SerialManager:
except Exception as e: except Exception as e:
logging.error(f"线径串口 {port_name} 读取线程异常: {e}") logging.error(f"线径串口 {port_name} 读取线程异常: {e}")
# 线程异常时,尝试重置状态
self.running_flags[port_name] = False
def _process_diameter_response(self, port_name, response_bytes: bytes): def _process_diameter_response(self, port_name, response_bytes: bytes):
"""处理线径响应数据""" """处理线径响应数据"""
@ -1137,6 +1207,7 @@ class SerialManager:
port_name: 串口名称 port_name: 串口名称
""" """
try: try:
logging.info(f"[{port_name}] 扫码器线程启动")
while self.running_flags.get(port_name, False): while self.running_flags.get(port_name, False):
if not self.is_port_open(port_name): if not self.is_port_open(port_name):
time.sleep(0.1) time.sleep(0.1)
@ -1151,6 +1222,8 @@ class SerialManager:
except Exception as e: except Exception as e:
logging.error(f"扫码器串口 {port_name} 读取线程异常: {e}") logging.error(f"扫码器串口 {port_name} 读取线程异常: {e}")
# 线程异常时,尝试重置状态
self.running_flags[port_name] = False
def _process_scanner_response(self, port_name, response_bytes: bytes): def _process_scanner_response(self, port_name, response_bytes: bytes):
"""处理扫码器响应数据""" """处理扫码器响应数据"""
@ -1197,4 +1270,59 @@ class SerialManager:
return False return False
except Exception as e: except Exception as e:
logging.error(f"处理扫码数据总体异常: {e}") logging.error(f"处理扫码数据总体异常: {e}")
return False return False
def _start_thread_monitor(self):
"""启动线程监控"""
threading.Thread(target=self._monitor_threads, daemon=True).start()
def _monitor_threads(self):
"""监控线程状态"""
try:
logging.info("线程监控已启动")
while True:
try:
# 创建当前线程的副本,避免在迭代过程中修改字典
thread_items = list(self.read_threads.items())
for port_name, thread in thread_items:
# 检查线程是否存活
if not thread.is_alive():
# 检查串口是否仍然打开
if port_name in self.serial_ports and self.is_port_open(port_name):
port_type = self.port_types.get(port_name)
callback = self.callbacks.get(port_name)
logging.warning(f"线程 {thread.name} 已终止但串口仍然打开,尝试重新启动线程")
# 重置线程状态
self.running_flags[port_name] = False
if port_name in self.read_threads:
self.read_threads.pop(port_name, None)
# 如果有端口类型记录,尝试重新启动线程
if port_type:
# 短暂等待,确保资源释放
time.sleep(0.5)
try:
# 重新打开串口
self.open_port(port_name, port_type, callback=callback)
logging.info(f"已重新启动线程: {port_name} ({port_type})")
except Exception as restart_error:
logging.error(f"重新启动线程失败: {port_name}, 错误: {restart_error}")
else:
logging.warning(f"无法重启线程: {port_name}, 未找到端口类型记录")
else:
# 串口已关闭,清理线程记录
if port_name in self.read_threads:
self.read_threads.pop(port_name, None)
logging.info(f"线程 {thread.name} 已终止,串口已关闭,清理线程记录")
# 每隔5秒检查一次
time.sleep(5)
except Exception as loop_error:
logging.error(f"线程监控循环异常: {loop_error}")
time.sleep(5) # 出错后等待一段时间再继续
except Exception as e:
logging.error(f"线程监控主循环异常: {e}")
# 尝试重新启动监控
time.sleep(10)
self._start_thread_monitor()

View File

@ -1,5 +1,6 @@
import sys import sys
import logging import logging
import threading
from utils.config_loader import ConfigLoader from utils.config_loader import ConfigLoader
try: try:
@ -21,6 +22,8 @@ except ImportError:
class SQLUtils: class SQLUtils:
# 存储连接池,避免重复创建连接 # 存储连接池,避免重复创建连接
_connection_pool = {} _connection_pool = {}
# 添加线程锁用于防止多线程同时使用同一个cursor
_lock = threading.RLock()
def __init__(self, db_type=None, source_name=None, **kwargs): def __init__(self, db_type=None, source_name=None, **kwargs):
"""初始化SQLUtils对象 """初始化SQLUtils对象
@ -83,6 +86,15 @@ class SQLUtils:
# 尝试从连接池获取连接,如果没有则创建新连接 # 尝试从连接池获取连接,如果没有则创建新连接
self._get_connection() self._get_connection()
def __enter__(self):
"""上下文管理器入口方法支持with语句"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""上下文管理器退出方法,自动关闭游标"""
self.close()
return False # 不抑制异常
def _get_connection(self): def _get_connection(self):
"""从连接池获取连接,如果没有则创建新连接""" """从连接池获取连接,如果没有则创建新连接"""
@ -90,25 +102,26 @@ class SQLUtils:
conn_key = f"{self.db_type}:{str(self.kwargs)}" conn_key = f"{self.db_type}:{str(self.kwargs)}"
# 检查连接池中是否已有此连接 # 检查连接池中是否已有此连接
if conn_key in SQLUtils._connection_pool: with SQLUtils._lock:
try: if conn_key in SQLUtils._connection_pool:
# 尝试执行简单查询,确认连接有效 try:
conn, cursor = SQLUtils._connection_pool[conn_key] # 尝试执行简单查询,确认连接有效
cursor.execute("SELECT 1") conn, cursor = SQLUtils._connection_pool[conn_key]
# 连接有效,直接使用 cursor.execute("SELECT 1")
self.conn = conn # 连接有效,直接使用
self.cursor = cursor self.conn = conn
return self.cursor = cursor
except Exception: return
# 连接已失效,从连接池移除 except Exception:
del SQLUtils._connection_pool[conn_key] # 连接已失效,从连接池移除
del SQLUtils._connection_pool[conn_key]
# 创建新连接
self.connect() # 创建新连接
self.connect()
# 将新连接添加到连接池
if self.conn and self.cursor: # 将新连接添加到连接池
SQLUtils._connection_pool[conn_key] = (self.conn, self.cursor) if self.conn and self.cursor:
SQLUtils._connection_pool[conn_key] = (self.conn, self.cursor)
def connect(self): def connect(self):
"""连接到数据库""" """连接到数据库"""
@ -120,7 +133,7 @@ class SQLUtils:
elif self.db_type in ['sqlite', 'sqlite3']: elif self.db_type in ['sqlite', 'sqlite3']:
if not sqlite3: if not sqlite3:
raise ImportError('sqlite3 is not installed') raise ImportError('sqlite3 is not installed')
self.conn = sqlite3.connect(self.kwargs.get('database', ':memory:')) self.conn = sqlite3.connect(self.kwargs.get('database', ':memory:'), check_same_thread=False)
elif self.db_type == 'mysql': elif self.db_type == 'mysql':
if not mysql: if not mysql:
raise ImportError('mysql.connector is not installed') raise ImportError('mysql.connector is not installed')
@ -137,61 +150,92 @@ class SQLUtils:
def execute_query(self, sql, params=None): def execute_query(self, sql, params=None):
if params is None: if params is None:
params = () params = ()
self.cursor.execute(sql, params) with SQLUtils._lock:
self.conn.commit() try:
self.cursor.execute(sql, params)
return self.cursor
except Exception as e:
logging.error(f"执行查询失败: {e}, SQL: {sql}, 参数: {params}")
raise
def execute_update(self, sql, params=None): def execute_update(self, sql, params=None):
try: try:
self.cursor.execute(sql,params) with SQLUtils._lock:
self.conn.commit() self.cursor.execute(sql, params)
self.conn.commit()
return self.cursor.rowcount
except Exception as e: except Exception as e:
self.conn.rollback() self.conn.rollback()
logging.error(f"执行更新失败: {e}, SQL: {sql}, 参数: {params}")
raise e raise e
def begin_transaction(self) -> None: def begin_transaction(self) -> None:
"""开始事务""" """开始事务"""
if self.db_type in ['sqlite', 'sqlite3']: with SQLUtils._lock:
self.execute_query('BEGIN TRANSACTION') if self.db_type in ['sqlite', 'sqlite3']:
else: self.execute_query('BEGIN TRANSACTION')
self.conn.autocommit = False else:
self.conn.autocommit = False
def commit_transaction(self) -> None: def commit_transaction(self) -> None:
"""提交事务""" """提交事务"""
self.conn.commit() with SQLUtils._lock:
if self.db_type not in ['sqlite', 'sqlite3']: self.conn.commit()
self.conn.autocommit = True if self.db_type not in ['sqlite', 'sqlite3']:
self.conn.autocommit = True
def rollback_transaction(self) -> None: def rollback_transaction(self) -> None:
"""回滚事务""" """回滚事务"""
self.conn.rollback() with SQLUtils._lock:
if self.db_type not in ['sqlite', 'sqlite3']: self.conn.rollback()
self.conn.autocommit = True if self.db_type not in ['sqlite', 'sqlite3']:
self.conn.autocommit = True
def fetchone(self): def fetchone(self):
return self.cursor.fetchone() with SQLUtils._lock:
return self.cursor.fetchone()
def fetchall(self): def fetchall(self):
return self.cursor.fetchall() with SQLUtils._lock:
return self.cursor.fetchall()
def close(self): def close(self):
"""关闭连接(实际上是将连接返回到连接池)""" """关闭当前游标,但保留连接在连接池中"""
# 这里不再实际关闭连接,让连接池管理连接生命周期 # 不再关闭连接,只关闭游标,减少重复创建连接的开销
# 连接仍然保留在连接池中供后续使用
pass pass
def real_close(self):
"""真正关闭连接,从连接池中移除"""
conn_key = f"{self.db_type}:{str(self.kwargs)}"
with SQLUtils._lock:
if conn_key in SQLUtils._connection_pool:
try:
conn, cursor = SQLUtils._connection_pool[conn_key]
if cursor:
cursor.close()
if conn:
conn.close()
del SQLUtils._connection_pool[conn_key]
logging.debug(f"已关闭并移除连接: {conn_key}")
except Exception as e:
logging.error(f"关闭连接失败: {e}")
@staticmethod @staticmethod
def close_all_connections(): def close_all_connections():
"""关闭所有连接池中的连接""" """关闭所有连接池中的连接"""
for conn, cursor in SQLUtils._connection_pool.values(): with SQLUtils._lock:
try: for conn, cursor in SQLUtils._connection_pool.values():
if cursor: try:
cursor.close() if cursor:
if conn: cursor.close()
conn.close() if conn:
except Exception as e: conn.close()
logging.error(f"关闭数据库连接失败: {e}") except Exception as e:
logging.error(f"关闭数据库连接失败: {e}")
SQLUtils._connection_pool.clear()
logging.info("已关闭所有数据库连接") SQLUtils._connection_pool.clear()
logging.info("已关闭所有数据库连接")
@staticmethod @staticmethod
def get_sqlite_connection(): def get_sqlite_connection():

View File

@ -882,7 +882,7 @@ class MainWindow(MainWindowUI):
'remark': '', 'remark': '',
'tray_id': tray_id 'tray_id': tray_id
}] }]
inspection_dao.save_inspection_data(self._current_order_code,gc_note,gc_note, data) inspection_dao.save_inspection_data(self._current_order_code,gc_note, data)
# 为贴标和称重也创建空记录 # 为贴标和称重也创建空记录
for position in [11, 12, 13]: # 11是贴标12是毛重13是净重 for position in [11, 12, 13]: # 11是贴标12是毛重13是净重
@ -2604,15 +2604,29 @@ class MainWindow(MainWindowUI):
gc_note = data_str.split("扫码数据:")[1].strip() gc_note = data_str.split("扫码数据:")[1].strip()
logging.info(f"提取到工程号: {gc_note}") logging.info(f"提取到工程号: {gc_note}")
# 临时断开returnPressed信号连接避免setText触发信号
try:
self.order_edit.returnPressed.disconnect(self.handle_order_enter)
except Exception:
logging.debug("returnPressed信号未连接或断开失败")
# 设置工程号到输入框 # 设置工程号到输入框
self.order_edit.setText(gc_note) self.order_edit.setText(gc_note)
# 模拟按下回车键触发handle_order_enter方法 # 重新连接returnPressed信号
self.order_edit.returnPressed.connect(self.handle_order_enter)
# 调用一次handle_order_enter方法
self.handle_order_enter() self.handle_order_enter()
else: else:
logging.warning(f"收到的数据不包含扫码数据标记: {data_str}") logging.warning(f"收到的数据不包含扫码数据标记: {data_str}")
except Exception as e: except Exception as e:
logging.error(f"处理扫码器数据失败: {str(e)}") logging.error(f"处理扫码器数据失败: {str(e)}")
# 确保信号重新连接
try:
self.order_edit.returnPressed.connect(self.handle_order_enter)
except Exception:
pass
def set_inspection_value(self, data_type, config, value): def set_inspection_value(self, data_type, config, value):
"""设置检验项目值到表格中 """设置检验项目值到表格中