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

386 lines
16 KiB
Python
Raw Normal View History

#!/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()