Files
dtm-py-all/UI/viewmodels/test_monitor_viewmodel.py

386 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
试验监控 ViewModel管理实时机台和工位数据
"""
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
from typing import Dict, List, Any
from models.system_model import SystemModel
class TestMonitorViewModel(QObject):
"""试验监控 ViewModel - 响应式数据管理"""
# 信号定义
machinesChanged = pyqtSignal() # 机台列表变化
stationsChanged = pyqtSignal() # 工位数据变化(预留)
connectionStatusChanged = pyqtSignal() # WebSocket 连接状态变化
def __init__(self, parent=None):
super().__init__(parent)
self._machines = {} # {machine_sn: machine_data}
self._stations = {} # {station_key: station_data}(预留,待后续完善)
self._connected = False
# 启动时从数据库加载机台配置,确保未连接也能显示
self._load_from_db()
# ===================== 响应式属性 =====================
@pyqtProperty('QVariantMap', notify=machinesChanged)
def machines(self):
"""所有机台数据(字典格式:{machine_sn: machine_data}"""
return self._machines
@pyqtProperty('QVariantList', notify=machinesChanged)
def machineList(self):
"""机台列表(数组格式,方便界面遍历)"""
return list(self._machines.values())
@pyqtProperty('QVariantMap', notify=stationsChanged)
def stations(self):
"""所有工位数据(预留)"""
return self._stations
@pyqtProperty(bool, notify=connectionStatusChanged)
def connected(self):
"""WebSocket 连接状态"""
return self._connected
def _load_from_db(self):
"""从数据库加载机台配置,构建初始显示数据"""
try:
model = SystemModel()
machines = model.load_machines() or []
for m in machines:
sn = m.get('SN') or ''
if not sn:
continue
# 构造与 WebSocket 保持一致的数据结构
machine_data = {
'label': m.get('label', ''),
'SN': sn,
'com': m.get('com', ''),
'testType': m.get('testType', ''),
'plcAddress': m.get('plcAddress', ''),
'type': m.get('type', ''),
# stations: 根据 station1~4 生成,'NA' 表示未启用
'stations': []
}
stations = []
for i in range(1, 5):
station_val = m.get(f'station{i}', '')
if station_val and str(station_val).upper() != 'NA':
stations.append({
'dtMachineSN': sn,
'dtMachineLabel': m.get('label', ''),
'label': f'工位{i}',
'SN': str(station_val),
'key': i - 1,
'formal': True,
# 初始状态:未连接时统一显示 idle后续由 WebSocket 更新)
'status': 'idle',
'index': 0,
'dutStatus': '',
})
machine_data['stations'] = stations
self._machines[sn] = machine_data
# 触发界面构建
self.machinesChanged.emit()
except Exception as e:
print(f'加载机台配置失败: {e}')
def updateMachineData(self, message_data: Dict[str, Any]):
"""
更新机台数据(由 WebSocket 消息触发,增量合并)
消息格式示例:
{
'status': 'success',
'command': 'machine_data_change',
'data': {
'machines': [
{
'label': 'Phone试验1#机-com7',
'SN': 'SUN-LAB1-TOP-001',
'com': 'com7',
'testType': 'Phone',
'plcAddress': '01',
'type': 'xinjie',
'comm_config': {...},
'connectState': 1,
'stations': [
{
'dtMachineSN': 'SUN-LAB1-TOP-001',
'dtMachineLabel': 'Phone试验1#机-com7',
'label': '工位1',
'SN': '01',
'status': 'running',
'dutStatus': '试验中',
'dutSN': '543009865',
'dut': {
'testReqList': [ # 跌落参数数据
{'dropCode': 1, 'item': '背面', 'height': 75.0, 'count': 100},
...
]
}
},
...
]
}
]
},
'machine_sn': 'SUN-LAB1-TOP-001'
}
"""
try:
data = message_data.get('data', {})
machines = data.get('machines', [])
for machine in machines:
sn = machine.get('SN', '')
if not sn:
continue
# 增量合并:如果机台已存在,合并字段;否则新建
existing_machine = self._machines.get(sn)
if existing_machine:
# 合并机台级字段(只更新消息中存在的字段)
for key in ('label', 'com', 'testType', 'plcAddress', 'type', 'comm_config', 'tcp_modbus_unit', 'connectState', 'key'):
if key in machine:
existing_machine[key] = machine[key]
# 合并stations字段增量更新
if 'stations' in machine:
new_stations = machine['stations']
existing_stations = existing_machine.get('stations', [])
# 按SN索引构建映射
station_map = {st.get('SN'): st for st in existing_stations}
for new_st in new_stations:
st_sn = new_st.get('SN', '')
if st_sn:
if st_sn in station_map:
# 增量合并station字段
self._merge_station_data(station_map[st_sn], new_st)
else:
# 新增station
self._process_station_data(new_st)
station_map[st_sn] = new_st
# 更新回机台
existing_machine['stations'] = list(station_map.values())
else:
# 新机台处理testReqList -> dropDirections转换
if 'stations' in machine:
for station in machine['stations']:
self._process_station_data(station)
self._machines[sn] = machine
# 触发响应式刷新
self.machinesChanged.emit()
except Exception as e:
print(f"更新机台数据失败: {e}")
import traceback
traceback.print_exc()
def _merge_station_data(self, existing_station: Dict[str, Any], new_station: Dict[str, Any]):
"""增量合并station数据只更新新消息中存在的字段"""
# 合并所有新字段
for key, value in new_station.items():
if key == 'dut':
# dut字段特殊处理增量合并
existing_dut = existing_station.get('dut', {})
new_dut = new_station.get('dut', {})
if isinstance(existing_dut, dict) and isinstance(new_dut, dict):
existing_dut.update(new_dut)
existing_station['dut'] = existing_dut
# 处理testReqList -> dropDirections
if 'testReqList' in existing_dut:
existing_station['dropDirections'] = self._convert_test_req_to_drop_directions(existing_dut['testReqList'])
else:
existing_station['dut'] = new_dut
else:
existing_station[key] = value
# 额外处理若新station中有dut但无dropDirections从testReqList转换
if 'dut' in existing_station and 'dropDirections' not in existing_station:
dut = existing_station.get('dut', {})
if isinstance(dut, dict) and 'testReqList' in dut:
existing_station['dropDirections'] = self._convert_test_req_to_drop_directions(dut['testReqList'])
def _process_station_data(self, station: Dict[str, Any]):
"""处理station数据将testReqList转换为dropDirections"""
dut = station.get('dut', {})
if isinstance(dut, dict) and 'testReqList' in dut:
station['dropDirections'] = self._convert_test_req_to_drop_directions(dut['testReqList'])
def _convert_test_req_to_drop_directions(self, test_req_list: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""将testReqList转换为dropDirections格式"""
if not test_req_list or not isinstance(test_req_list, list):
return []
drop_directions = []
for req in test_req_list:
drop_directions.append({
'方向': req.get('item', ''),
'高度': req.get('height', 0),
'设定次数': req.get('count', 0),
'当前次数': req.get('itemCurrentCycles',0)
})
return drop_directions
def updateStationData(self, message_data: Dict[str, Any]):
"""
更新工位(通道)详细数据
参数:
message_data: WebSocket 消息数据(包含通道详细信息)
通道数据结构:
{
'dtMachineSN': 'SUN-LAB1-TOP-001',
'label': '工位1',
'SN': '01',
'status': 'idle', // 工位状态
'dutStatus': '待启动', // DUT 状态
'dutName': '电池组成1型',
'dutSN': '202401032245180707',
'dutProject': 'DTM-PRJ-00002',
'dutPhase': 'DTM-PHASE-00002',
'projectName': 'GV9206 后视镜',
'projectPhase': 'B Sample TEST',
'dut': {详细的 DUT 信息...},
...
}
"""
try:
# 通道数据已包含在机台数据的 stations 字段中
# 此方法用于单独更新某个通道的详细数据(如有需要)
machine_sn = message_data.get('dtMachineSN', '')
station_sn = message_data.get('SN', '')
if machine_sn and station_sn:
station_key = self.getStationKey(machine_sn, station_sn)
self._stations[station_key] = message_data
print(f"更新工位数据: {machine_sn}:{station_sn}")
# 同时更新机台数据中的 stations 列表
machine = self._machines.get(machine_sn)
if machine:
stations = machine.get('stations', [])
station_found = False
for i, station in enumerate(stations):
if station.get('SN') == station_sn:
stations[i] = message_data
station_found = True
break
# 如果没有找到对应的站点,则添加新站点
if not station_found:
stations.append(message_data)
# 触发响应式刷新
self.machinesChanged.emit()
self.stationsChanged.emit()
except Exception as e:
print(f"更新工位数据失败: {e}")
import traceback
traceback.print_exc()
def updateStationDropResults(self, message_data: Dict[str, Any]):
"""更新工位(通道)跌落结果数据(增量合并)"""
try:
machine_sn = message_data.get('dtMachineSN', '') or message_data.get('machine_sn', '')
station_sn = message_data.get('SN', '') or message_data.get('station_sn', '')
if not machine_sn or not station_sn:
return
machine = self._machines.get(machine_sn)
if not machine:
return
stations = machine.get('stations', [])
for i, station in enumerate(stations):
if station.get('SN') == station_sn:
drop_dirs = message_data.get('dropDirections')
if drop_dirs is not None:
station['dropDirections'] = drop_dirs
for k in ('dropResult', 'currentDirection', 'currentCycles'):
if k in message_data:
station[k] = message_data[k]
stations[i] = station
break
self.stationsChanged.emit()
except Exception as e:
print(f"更新工位跌落结果失败: {e}")
import traceback
traceback.print_exc()
def setConnectionStatus(self, connected: bool):
"""设置 WebSocket 连接状态"""
if self._connected != connected:
self._connected = connected
self.connectionStatusChanged.emit()
# ===================== 查询方法 =====================
def getMachineByLabel(self, label: str) -> Dict[str, Any]:
"""根据标签获取机台数据"""
for machine in self._machines.values():
if machine.get('label') == label:
return machine
return {}
def getMachineBySN(self, sn: str) -> Dict[str, Any]:
"""根据 SN 获取机台数据"""
return self._machines.get(sn, {})
def getStationsByMachineSN(self, machine_sn: str) -> List[Dict[str, Any]]:
"""获取指定机台的所有工位"""
machine = self._machines.get(machine_sn, {})
return machine.get('stations', [])
def getStationKey(self, machine_sn: str, station_sn: str) -> str:
"""生成工位唯一标识"""
return f"{machine_sn}:{station_sn}"
def getStationDetailData(self, machine_sn: str, station_sn: str) -> Dict[str, Any]:
"""
获取工位详细数据(预留接口)
参数:
machine_sn: 机台 SN
station_sn: 工位 SN
返回:
工位详细数据字典(待后续完善)
"""
station_key = self.getStationKey(machine_sn, station_sn)
return self._stations.get(station_key, {})
# ===================== 分组方法 =====================
def getMachinesByTestType(self, test_type: str) -> List[Dict[str, Any]]:
"""按测试类型分组获取机台"""
return [
machine for machine in self._machines.values()
if machine.get('testType') == test_type
]
def getAllTestTypes(self) -> List[str]:
"""获取所有测试类型"""
test_types = set()
for machine in self._machines.values():
test_type = machine.get('testType', '')
if test_type:
test_types.add(test_type)
return sorted(list(test_types))
# ===================== 清理方法 =====================
def clear(self):
"""清空所有数据"""
self._machines.clear()
self._stations.clear()
self.machinesChanged.emit()
self.stationsChanged.emit()