From 7ae952f93ed40e989cffe21d8d2e41790e0d56e9 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 27 Mar 2026 10:39:17 +0800 Subject: [PATCH] feat(event): add RemLocal/RemRecovered events for REM-triggered auto-stop When any equipment's REM signal switches to local mode, fire a dedicated `unit.rem_local` event (with unit + equipment context) and record it to the event log. Also fire `unit.rem_recovered` when all REM signals return to remote. AutoControlStopped is still fired alongside RemLocal when auto was running at the time. Co-Authored-By: Claude Sonnet 4.6 --- src/control/engine.rs | 32 ++++++++++++++++++++++++++++---- src/event.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/control/engine.rs b/src/control/engine.rs index e2d5de0..e0b506c 100644 --- a/src/control/engine.rs +++ b/src/control/engine.rs @@ -326,6 +326,22 @@ async fn check_fault_comm( .unwrap_or(false) }); + // Find the first equipment that just switched to local (for event payload). + let rem_local_eq_id = if any_rem_local && !runtime.rem_local { + all_roles + .iter() + .find(|(_, roles)| { + roles + .get("rem") + .and_then(|rp| monitor.get(&rp.point_id)) + .map(|m| !super::monitor_value_as_bool(m) && m.quality == PointQuality::Good) + .unwrap_or(false) + }) + .map(|(eq_id, _)| *eq_id) + } else { + None + }; + drop(monitor); let prev_comm = runtime.comm_locked; @@ -362,10 +378,18 @@ async fn check_fault_comm( } } - // Stop auto-control when any equipment switches to local mode. - if any_rem_local && runtime.auto_enabled { - runtime.auto_enabled = false; - let _ = state.event_manager.send(AppEvent::AutoControlStopped { unit_id: unit.id }); + // Fire RemLocal event when any equipment first switches to local mode. + if let Some(eq_id) = rem_local_eq_id { + let _ = state.event_manager.send(AppEvent::RemLocal { unit_id: unit.id, equipment_id: eq_id }); + if runtime.auto_enabled { + runtime.auto_enabled = false; + let _ = state.event_manager.send(AppEvent::AutoControlStopped { unit_id: unit.id }); + } + } + + // Fire RemRecovered when all rem signals return to remote. + if prev_rem_local && !any_rem_local { + let _ = state.event_manager.send(AppEvent::RemRecovered { unit_id: unit.id }); } runtime.comm_locked != prev_comm diff --git a/src/event.rs b/src/event.rs index 9c3a026..a8a5650 100644 --- a/src/event.rs +++ b/src/event.rs @@ -41,6 +41,8 @@ pub enum AppEvent { FaultAcked { unit_id: Uuid }, CommLocked { unit_id: Uuid }, CommRecovered { unit_id: Uuid }, + RemLocal { unit_id: Uuid, equipment_id: Uuid }, + RemRecovered { unit_id: Uuid }, UnitStateChanged { unit_id: Uuid, from_state: String, to_state: String }, PointNewValue(crate::telemetry::PointNewValue), } @@ -245,6 +247,12 @@ async fn handle_control_event( AppEvent::CommRecovered { unit_id } => { tracing::info!("Comm recovered for unit {}", unit_id); } + AppEvent::RemLocal { unit_id, equipment_id } => { + tracing::warn!("REM local: unit={}, equipment={}", unit_id, equipment_id); + } + AppEvent::RemRecovered { unit_id } => { + tracing::info!("REM recovered for unit {}", unit_id); + } AppEvent::UnitStateChanged { unit_id, from_state, to_state } => { tracing::info!("Unit {} state: {} → {}", unit_id, from_state, to_state); } @@ -413,6 +421,25 @@ async fn persist_event_if_needed( serde_json::json!({ "unit_id": unit_id }), )) } + AppEvent::RemLocal { unit_id, equipment_id } => { + let unit_code = fetch_unit_code(pool, *unit_id).await; + let eq_code = fetch_equipment_code(pool, *equipment_id).await; + Some(( + "unit.rem_local", "warn", + Some(*unit_id), Some(*equipment_id), None, + format!("单元【{}】切换为本地控制,触发设备:{},自动控制已停止", unit_code, eq_code), + serde_json::json!({ "unit_id": unit_id, "equipment_id": equipment_id }), + )) + } + AppEvent::RemRecovered { unit_id } => { + let code = fetch_unit_code(pool, *unit_id).await; + Some(( + "unit.rem_recovered", "info", + Some(*unit_id), None, None, + format!("单元【{}】已切换回远程控制", code), + serde_json::json!({ "unit_id": unit_id }), + )) + } AppEvent::UnitStateChanged { .. } => None, AppEvent::PointNewValue(_) => None, };