状态更新

main
risingLee 2026-01-19 17:00:40 +08:00
parent bb1d70795a
commit 217290a76e
4 changed files with 321 additions and 39 deletions

View File

@ -115,45 +115,71 @@ class ExperimentStateMonitor:
"""监控循环 - 定时查询并检测状态变化"""
try:
logger.info(f"[监控循环] 开始监控实验{self.experiment_id}")
logger.info(
f"[监控循环] 配置信息: status_field={self.query_config.get('status_field')}, "
f"status_values={self.query_config.get('status_values')}"
)
while not self._stop_event.is_set():
try:
# 查询当前状态
logger.debug(f"[监控循环] 实验{self.experiment_id}开始查询状态...")
logger.info(f"[监控循环] 实验{self.experiment_id}开始查询状态...")
current_state = self._query_current_state()
logger.info(
f"[监控循环] 实验{self.experiment_id}查询结果: 当前状态={current_state}, "
f"上一状态={self._last_state}"
f"[监控循环] 实验{self.experiment_id}查询结果: 当前状态={current_state} (type={type(current_state).__name__ if current_state else 'None'}), "
f"上一状态={self._last_state} (type={type(self._last_state).__name__ if self._last_state else 'None'})"
)
if current_state is not None:
# 数值比较函数:支持字符串和数值的灵活比较
def states_equal(state1: str, state2: str) -> bool:
"""比较两个状态是否相等,支持数值比较"""
if state1 == state2:
return True
try:
return float(state1) == float(state2)
except (ValueError, TypeError):
return False
# 检测状态变化
if self._last_state is None:
# 第一次查询,记录初始状态
# 第一次查询,记录初始状态并检查是否为开始状态
self._last_state = current_state
logger.info(
f"[监控循环] 实验{self.experiment_id}初始状态: {current_state}"
)
elif current_state != self._last_state:
# 状态发生变化
old_state = self._last_state
self._last_state = current_state
logger.info(
f"[监控循环] 🔄 实验{self.experiment_id}状态变化: {old_state} -> {current_state}"
)
# 处理状态变化
self._handle_state_change(old_state, current_state)
# 检查初始状态是否为开始值,如果是则触发开始事件
self._check_initial_state(current_state)
else:
logger.debug(
f"[监控循环] 实验{self.experiment_id}状态无变化: {current_state}"
# 比较状态是否变化
is_equal = states_equal(current_state, self._last_state)
logger.info(
f"[监控循环-比较] 实验{self.experiment_id}: "
f"current_state='{current_state}', _last_state='{self._last_state}', "
f"states_equal={is_equal}, 字符串比较={(current_state == self._last_state)}"
)
if not is_equal:
# 状态发生变化(使用数值比较)
old_state = self._last_state
self._last_state = current_state
logger.info(
f"[监控循环] 🔄 实验{self.experiment_id}状态变化: {old_state} -> {current_state}"
)
# 处理状态变化
self._handle_state_change(old_state, current_state)
else:
logger.info(
f"[监控循环] 实验{self.experiment_id}状态无变化: {current_state}"
)
else:
logger.debug(f"[监控循环] 实验{self.experiment_id}暂无数据")
logger.info(f"[监控循环] 实验{self.experiment_id}暂无数据")
# 等待下一个轮询周期
logger.info(f"[监控循环] 实验{self.experiment_id}等待{self.poll_interval}秒后继续...")
self._stop_event.wait(self.poll_interval)
except Exception as e:
@ -237,9 +263,10 @@ class ExperimentStateMonitor:
# 构建时间范围查询最近1小时的数据
time_range = "-1h"
logger.debug(
logger.info(
f"[查询] 实验{self.experiment_id}查询InfluxDB: "
f"bucket={bucket}, measurement={measurement}, fields={fields}"
f"bucket={bucket}, measurement={measurement}, fields={fields}, "
f"filters={filters}, status_field={status_field}"
)
# 执行查询
@ -252,23 +279,52 @@ class ExperimentStateMonitor:
)
if df.empty:
logger.debug(f"[查询] 实验{self.experiment_id}查询结果为空")
logger.info(f"[查询] 实验{self.experiment_id}查询结果为空DataFrame为空")
return None
logger.info(
f"[查询] 实验{self.experiment_id}查询到 {len(df)} 条记录, "
f"列: {list(df.columns)}"
)
# 获取最新的状态值
if '_value' in df.columns and '_field' in df.columns:
# 显示所有字段
unique_fields = df['_field'].unique()
logger.info(
f"[查询] 实验{self.experiment_id}查询结果包含字段: {list(unique_fields)}"
)
# 筛选出指定字段的数据
status_data = df[df['_field'] == status_field]
logger.info(
f"[查询] 实验{self.experiment_id}筛选字段'{status_field}'后有 {len(status_data)} 条记录"
)
if not status_data.empty:
# 按时间排序,取最后一条记录
if '_time' in status_data.columns:
status_data = status_data.sort_values('_time')
logger.info(
f"[查询] 实验{self.experiment_id}时间范围: "
f"{status_data['_time'].iloc[0]}{status_data['_time'].iloc[-1]}"
)
# 显示最近几条记录
if len(status_data) > 0:
recent_count = min(5, len(status_data))
logger.info(
f"[查询] 实验{self.experiment_id}最近{recent_count}条记录的值: "
f"{list(status_data['_value'].tail(recent_count))}"
)
latest_value = status_data['_value'].iloc[-1]
logger.debug(
latest_time = status_data['_time'].iloc[-1] if '_time' in status_data.columns else 'N/A'
logger.info(
f"[查询] 实验{self.experiment_id}最新状态值: {latest_value} "
f"(字段: {status_field})"
f"(类型: {type(latest_value).__name__}, 字段: {status_field}, 时间: {latest_time})"
)
return str(latest_value)
else:
@ -350,6 +406,24 @@ class ExperimentStateMonitor:
f"(连续失败{self._consecutive_failures}/{self.max_consecutive_failures}次)"
)
def _try_float_compare(self, val1: str, val2: str) -> str:
"""
尝试浮点数比较用于调试
Args:
val1: 值1
val2: 值2
Returns:
比较结果的字符串描述
"""
try:
f1 = float(val1)
f2 = float(val2)
return f"float({val1})={f1}, float({val2})={f2}, 相等={f1 == f2}"
except (ValueError, TypeError) as e:
return f"转换失败: {e}"
def _handle_state_change(self, old_state: str, new_state: str) -> None:
"""
处理状态变化
@ -365,24 +439,126 @@ class ExperimentStateMonitor:
logger.info(
f"[状态变化] 实验{self.experiment_id}: {old_state} -> {new_state}, "
f"开始值={start_value}, 结束值={end_value}"
f"开始值配置={start_value} (type={type(start_value).__name__}), "
f"结束值配置={end_value} (type={type(end_value).__name__})"
)
# 类型转换和比较日志
old_state_str = str(old_state)
new_state_str = str(new_state)
start_value_str = str(start_value)
end_value_str = str(end_value)
logger.info(
f"[状态变化-详细] 实验{self.experiment_id}: "
f"old_state_str='{old_state_str}' (type={type(old_state).__name__}), "
f"new_state_str='{new_state_str}' (type={type(new_state).__name__}), "
f"start_value_str='{start_value_str}', "
f"end_value_str='{end_value_str}'"
)
logger.info(
f"[状态变化-标志] 实验{self.experiment_id}: "
f"_experiment_started={self._experiment_started}, "
f"_experiment_ended={self._experiment_ended}"
)
# 数值比较函数:支持字符串和数值的灵活比较
def values_equal(val1: str, val2: str) -> bool:
"""比较两个值是否相等,支持数值比较"""
# 先尝试字符串直接比较
if val1 == val2:
return True
# 尝试作为浮点数比较(处理 '1' vs '1.0' 的情况)
try:
return float(val1) == float(val2)
except (ValueError, TypeError):
return False
# 检测实验开始:状态从非开始值变为开始值
if not self._experiment_started and str(new_state) == str(start_value):
is_start_match = values_equal(new_state_str, start_value_str)
is_start_condition = not self._experiment_started and is_start_match
logger.info(
f"[状态变化-开始检测] 实验{self.experiment_id}: "
f"not _experiment_started={not self._experiment_started}, "
f"values_equal(new_state_str, start_value_str)={is_start_match}, "
f"触发开始={is_start_condition}"
)
if is_start_condition:
logger.info(f"[状态变化] ✅ 实验{self.experiment_id}满足开始条件,调用 _on_experiment_started()")
self._on_experiment_started()
# 检测实验结束:状态从开始值变回非开始值
elif self._experiment_started and str(new_state) == str(end_value):
# 检测实验结束:状态从开始值变回结束值
is_end_match = values_equal(new_state_str, end_value_str)
is_end_condition = self._experiment_started and is_end_match
logger.info(
f"[状态变化-结束检测] 实验{self.experiment_id}: "
f"_experiment_started={self._experiment_started}, "
f"new_state_str='{new_state_str}', end_value_str='{end_value_str}', "
f"values_equal(new_state_str, end_value_str)={is_end_match}, "
f"字符串比较=(new_state_str == end_value_str)={(new_state_str == end_value_str)}, "
f"浮点数比较尝试={self._try_float_compare(new_state_str, end_value_str)}, "
f"触发结束={is_end_condition}"
)
if is_end_condition:
logger.info(f"[状态变化] ✅ 实验{self.experiment_id}满足结束条件,调用 _on_experiment_ended()")
self._on_experiment_ended()
elif self._experiment_started and not is_end_match and not is_start_match:
# 只在状态既不是开始值也不是结束值时才警告(意外的中间状态)
logger.warning(
f"[状态变化] ⚠️ 实验{self.experiment_id}检测到意外状态: "
f"new_state_str='{new_state_str}' (期望: start='{start_value_str}' 或 end='{end_value_str}')"
)
# 触发回调
if self.on_state_changed:
logger.debug(f"[状态变化] 实验{self.experiment_id}触发状态变化回调")
self.on_state_changed(old_state, new_state)
except Exception as e:
logger.error(f"[状态变化] 实验{self.experiment_id}处理失败: {e}", exc_info=True)
def _check_initial_state(self, initial_state: str) -> None:
"""
检查初始状态如果是开始值则触发开始事件
Args:
initial_state: 初始状态值
"""
try:
status_values = self.query_config.get('status_values', {})
start_value = str(status_values.get('start', ''))
initial_state_str = str(initial_state)
# 数值比较函数
def values_equal(val1: str, val2: str) -> bool:
if val1 == val2:
return True
try:
return float(val1) == float(val2)
except (ValueError, TypeError):
return False
is_start_match = values_equal(initial_state_str, start_value)
logger.info(
f"[初始状态检查] 实验{self.experiment_id}: "
f"initial_state='{initial_state_str}', start_value='{start_value}', "
f"匹配={is_start_match}"
)
if is_start_match and not self._experiment_started:
logger.info(f"[初始状态检查] ✅ 实验{self.experiment_id}初始状态即为开始状态,触发开始事件")
self._on_experiment_started()
except Exception as e:
logger.error(f"[初始状态检查] 实验{self.experiment_id}检查失败: {e}", exc_info=True)
def _on_experiment_started(self) -> None:
"""实验开始事件处理"""
try:
@ -421,6 +597,19 @@ class ExperimentStateMonitor:
f"记录时间: {self._end_time_recorded}"
)
# 验证实验时长
if not self._validate_experiment_duration():
logger.warning(
f"[实验结束] ⚠️ 实验{self.experiment_id}时长不足3.5小时,"
f"标记为异常数据,不入库"
)
# 标记为作废状态
self._mark_experiment_as_invalid()
# 设置停止标志
self._stop_event.set()
logger.info(f"[实验结束] 🛑 异常实验已标记作废,监控退出")
return
# 更新数据库设置end_ts
self._update_experiment_end_time(self._end_time_recorded)
@ -465,6 +654,82 @@ class ExperimentStateMonitor:
)
return False
def _validate_experiment_duration(self) -> bool:
"""验证实验时长是否满足最小要求3.5小时)"""
try:
if not self._start_time_recorded or not self._end_time_recorded:
logger.warning(f"[时长验证] 实验{self.experiment_id}缺少开始或结束时间")
return False
# 解析时间
start_dt = datetime.datetime.fromisoformat(self._start_time_recorded)
end_dt = datetime.datetime.fromisoformat(self._end_time_recorded)
# 计算时长(小时)
duration = (end_dt - start_dt).total_seconds() / 3600.0
logger.info(
f"[时长验证] 实验{self.experiment_id}持续时长: {duration:.2f}小时 "
f"(开始: {self._start_time_recorded}, 结束: {self._end_time_recorded})"
)
# 最小时长要求3.5小时
MIN_DURATION_HOURS = 3.5
if duration < MIN_DURATION_HOURS:
logger.warning(
f"[时长验证] ❌ 实验{self.experiment_id}时长{duration:.2f}小时 < "
f"{MIN_DURATION_HOURS}小时,不满足要求"
)
return False
logger.info(
f"[时长验证] ✅ 实验{self.experiment_id}时长{duration:.2f}小时 >= "
f"{MIN_DURATION_HOURS}小时,满足要求"
)
return True
except Exception as e:
logger.error(
f"[时长验证] 实验{self.experiment_id}验证失败: {e}",
exc_info=True
)
return False
def _mark_experiment_as_invalid(self) -> None:
"""标记实验为作废状态(异常数据)"""
try:
from pathlib import Path
db_path = Path(__file__).parent / "experiments.db"
db = sqlite3.connect(str(db_path))
cur = db.cursor()
# 更新实验状态为作废,设置 is_terminated=1并记录结束时间
cur.execute(
"""UPDATE experiments
SET end_ts=?,
is_terminated=1,
remark=CASE
WHEN remark IS NULL OR remark='' THEN '作废-时长不足3.5小时'
ELSE remark || ' [作废-时长不足3.5小时]'
END
WHERE id=?""",
(self._end_time_recorded, self.experiment_id)
)
db.commit()
db.close()
logger.info(
f"[作废标记] ✅ 实验{self.experiment_id}已标记为作废状态 (is_terminated=1)"
)
except Exception as e:
logger.error(
f"[作废标记] 实验{self.experiment_id}标记失败: {e}",
exc_info=True
)
def _update_experiment_start_time(self, start_time: str) -> None:
"""更新实验记录的开始时间"""
try:

View File

@ -26,9 +26,9 @@ def get_logger(name: str = "docx_creator") -> logging.Logger:
fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
handler.setFormatter(fmt)
logger.addHandler(handler)
# also log warnings+ to stderr for debugging
# also log INFO+ to stderr for debugging
stream = logging.StreamHandler()
stream.setLevel(logging.WARNING)
stream.setLevel(logging.INFO) # 改为 INFO 级别
stream.setFormatter(fmt)
logger.addHandler(stream)
_LOGGER = logger

View File

@ -887,21 +887,24 @@ class Breaker:
self.optFlag = 3
def alarming(self):
"""报警时:红灯亮+蜂鸣器响,绿灯灭"""
if not self.alarm and self.closed & 0xFF == 0xF0:
self.exeCmd('turnOffGreen')
self.exeCmd('turnOnRed')
self.exeCmd('turnOnAlarm')
self.alarm = 1 # 设置报警标志
def unalarming(self):
"""解除报警:根据合闸状态控制指示灯"""
if self.alarm:
self.exeCmd('turnOffRed')
self.exeCmd('turnOffAlarm')
# 如果是合闸状态,恢复绿灯
if self.closed & 0xFF == 0xF0:
self.exeCmd('turnOnGreen')
self.exeCmd('turnOffRed')
self.exeCmd('turnOffAlarm')
else:
self.exeCmd('turnOffGreen')
self.exeCmd('turnOffRed')
self.exeCmd('turnOffAlarm')
self.alarm = 0 # 清除报警标志
def open(self):
"""打开串口连接"""
@ -926,6 +929,10 @@ class Breaker:
case 0:
if self.open() == 0:
ret0 = self.exeCmd('openBreaker')
# 初始化时关闭所有指示灯
self.exeCmd('turnOffGreen')
self.exeCmd('turnOffRed')
self.exeCmd('turnOffAlarm')
# self.logger.info(f"setOverLimitValues ret: {ret0}")
ret1 = self.exeCmd('setOverLimitValues')
# self.logger.info(f"setOverLimitValues ret: {ret1}")
@ -952,6 +959,11 @@ class Breaker:
case 2:
ret = self.exeCmd('openBreaker')
if ret[0]:
# 分闸成功后,关闭所有指示灯
self.exeCmd('turnOffGreen')
self.exeCmd('turnOffRed')
self.exeCmd('turnOffAlarm')
self.alarm = 0 # 清除报警标志
self.optFlag = 1
continue
self.optFlag = -1
@ -959,6 +971,9 @@ class Breaker:
case 3:
ret = self.exeCmd('closeBreaker')
if ret[0]:
# 合闸成功后,点亮绿灯(如果没有报警)
# if not self.alarm:
self.exeCmd('turnOnGreen')
self.optFlag = 1
continue
self.optFlag = -1

View File

@ -5039,16 +5039,18 @@ class MainWindow(QMainWindow):
if row:
start_ts, end_ts = row
# 如果实验已完成(有结束时间),退出等待状态
# 只有实验已完成(有结束时间)时,才退出等待状态
if end_ts is not None:
self.logger.info(f"[等待状态] ✅ 实验 {self._waiting_experiment_id} 已完成,自动退出等待状态")
self.statusBar().showMessage("✅ 实验已完成", 3000)
self._exit_waiting_state()
elif start_ts is not None:
# 实验已开始(进行中),也退出等待状态
self.logger.info(f"[等待状态] 🔄 实验 {self._waiting_experiment_id} 已开始,自动退出等待状态")
self.statusBar().showMessage("🔄 实验已开始", 3000)
self._exit_waiting_state()
# 实验已开始但未结束,更新提示为"等待实验结束"
self.logger.info(f"[等待状态] 🔄 实验 {self._waiting_experiment_id} 已开始,继续监控等待结束")
# 更新等待标签文本
work_order_no = self.waiting_label.text().split("工单号: ")[-1] if "工单号:" in self.waiting_label.text() else "未知"
self.waiting_label.setText(f"⏳ 等待实验结束 - 工单号: {work_order_no}")
self.statusBar().showMessage("🔄 实验进行中,等待结束...", 3000)
else:
self.logger.info(f"[等待状态] ⏳ 实验 {self._waiting_experiment_id} 仍在等待开始")
else:
@ -5207,7 +5209,7 @@ class MainWindow(QMainWindow):
query_config=query_config,
on_state_changed=self._on_monitor_state_changed,
on_connection_changed=self._on_monitor_connection_changed,
poll_interval=5 # 每5秒查询一次
poll_interval=1 # 每1秒查询一次避免错过快速状态变化
)
# 启动监控器