feat(control): stop auto-control and disable buttons when REM goes local
- Add `rem_local: bool` to UnitRuntime; set true when any equipment's REM signal is false with good quality - Engine check_fault_comm: stop auto-control and fire AutoControlStopped when any equipment switches to local mode - Block start-auto when rem_local (backend + error message) - Frontend: disable Start Auto button in units/ops views when rem_local - Frontend: disable equipment Start/Stop buttons in config view when unit's rem_local is true Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1354f89204
commit
4227747852
|
|
@ -317,6 +317,15 @@ async fn check_fault_comm(
|
|||
None
|
||||
};
|
||||
|
||||
// REM local: any equipment with a rem point that is explicitly false (local mode) with good quality.
|
||||
let any_rem_local = all_roles.iter().any(|(_, 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)
|
||||
});
|
||||
|
||||
drop(monitor);
|
||||
|
||||
let prev_comm = runtime.comm_locked;
|
||||
|
|
@ -324,9 +333,11 @@ async fn check_fault_comm(
|
|||
let prev_fault_locked = runtime.fault_locked;
|
||||
let prev_auto = runtime.auto_enabled;
|
||||
let prev_ack = runtime.manual_ack_required;
|
||||
let prev_rem_local = runtime.rem_local;
|
||||
|
||||
runtime.comm_locked = any_bad;
|
||||
runtime.flt_active = any_flt;
|
||||
runtime.rem_local = any_rem_local;
|
||||
|
||||
if !prev_comm && runtime.comm_locked {
|
||||
let _ = state.event_manager.send(AppEvent::CommLocked { unit_id: unit.id });
|
||||
|
|
@ -351,11 +362,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 });
|
||||
}
|
||||
|
||||
runtime.comm_locked != prev_comm
|
||||
|| runtime.flt_active != prev_flt
|
||||
|| runtime.fault_locked != prev_fault_locked
|
||||
|| runtime.auto_enabled != prev_auto
|
||||
|| runtime.manual_ack_required != prev_ack
|
||||
|| runtime.rem_local != prev_rem_local
|
||||
}
|
||||
|
||||
type EquipMaps = (
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ pub struct UnitRuntime {
|
|||
pub flt_active: bool,
|
||||
pub comm_locked: bool,
|
||||
pub manual_ack_required: bool,
|
||||
/// True when any equipment in the unit has REM=false (local mode) with good signal quality.
|
||||
pub rem_local: bool,
|
||||
}
|
||||
|
||||
impl UnitRuntime {
|
||||
|
|
@ -39,6 +41,7 @@ impl UnitRuntime {
|
|||
flt_active: false,
|
||||
comm_locked: false,
|
||||
manual_ack_required: false,
|
||||
rem_local: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ fn validate_unit_timing_order(
|
|||
}
|
||||
|
||||
fn auto_control_start_blocked(runtime: &crate::control::runtime::UnitRuntime) -> bool {
|
||||
runtime.fault_locked || runtime.comm_locked || runtime.manual_ack_required
|
||||
runtime.fault_locked || runtime.comm_locked || runtime.manual_ack_required || runtime.rem_local
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
|
|
@ -543,6 +543,8 @@ pub async fn start_auto_unit(
|
|||
"Unit is fault locked, cannot start auto control"
|
||||
} else if runtime.comm_locked {
|
||||
"Unit communication is locked, cannot start auto control"
|
||||
} else if runtime.rem_local {
|
||||
"Equipment is in local mode (REM off), cannot start auto control"
|
||||
} else {
|
||||
"Fault acknowledgement required before starting auto control"
|
||||
};
|
||||
|
|
@ -740,6 +742,7 @@ mod tests {
|
|||
flt_active: false,
|
||||
comm_locked: true,
|
||||
manual_ack_required: false,
|
||||
rem_local: false,
|
||||
};
|
||||
|
||||
assert!(auto_control_start_blocked(&runtime));
|
||||
|
|
|
|||
|
|
@ -208,9 +208,14 @@ export function renderEquipments() {
|
|||
actionRow.append(editBtn, deleteBtn);
|
||||
|
||||
if (equipment.kind === "coal_feeder" || equipment.kind === "distributor") {
|
||||
const unitRuntime = equipment.unit_id ? state.runtimes.get(equipment.unit_id) : null;
|
||||
const remLocal = unitRuntime?.rem_local ?? false;
|
||||
|
||||
const startBtn = document.createElement("button");
|
||||
startBtn.className = "secondary";
|
||||
startBtn.textContent = "Start";
|
||||
startBtn.disabled = remLocal;
|
||||
startBtn.title = remLocal ? "设备处于本地模式(REM关)" : "";
|
||||
startBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
apiFetch(`/api/control/equipment/${equipment.id}/start`, { method: "POST" })
|
||||
|
|
@ -220,6 +225,8 @@ export function renderEquipments() {
|
|||
const stopBtn = document.createElement("button");
|
||||
stopBtn.className = "danger";
|
||||
stopBtn.textContent = "Stop";
|
||||
stopBtn.disabled = remLocal;
|
||||
stopBtn.title = remLocal ? "设备处于本地模式(REM关)" : "";
|
||||
stopBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
apiFetch(`/api/control/equipment/${equipment.id}/stop`, { method: "POST" })
|
||||
|
|
|
|||
|
|
@ -55,13 +55,15 @@ export function renderOpsUnits() {
|
|||
const actions = item.querySelector(".ops-unit-item-actions");
|
||||
|
||||
const isAutoOn = runtime?.auto_enabled;
|
||||
const startBlocked = !isAutoOn && (runtime?.fault_locked || runtime?.manual_ack_required);
|
||||
const startBlocked = !isAutoOn && (runtime?.fault_locked || runtime?.manual_ack_required || runtime?.rem_local);
|
||||
const autoBtn = document.createElement("button");
|
||||
autoBtn.className = isAutoOn ? "danger" : "secondary";
|
||||
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
|
||||
autoBtn.disabled = startBlocked;
|
||||
autoBtn.title = startBlocked
|
||||
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制" : "需人工确认故障后才可启动自动控制")
|
||||
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制"
|
||||
: runtime?.rem_local ? "设备处于本地模式(REM关),无法启动自动控制"
|
||||
: "需人工确认故障后才可启动自动控制")
|
||||
: (isAutoOn ? "停止自动控制" : "启动自动控制");
|
||||
autoBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -151,13 +151,15 @@ export function renderUnits() {
|
|||
actions.append(editBtn, deleteBtn);
|
||||
|
||||
const isAutoOn = runtime?.auto_enabled;
|
||||
const startBlocked = !isAutoOn && (runtime?.fault_locked || runtime?.manual_ack_required);
|
||||
const startBlocked = !isAutoOn && (runtime?.fault_locked || runtime?.manual_ack_required || runtime?.rem_local);
|
||||
const autoBtn = document.createElement("button");
|
||||
autoBtn.className = isAutoOn ? "danger" : "secondary";
|
||||
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
|
||||
autoBtn.disabled = startBlocked;
|
||||
autoBtn.title = startBlocked
|
||||
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制" : "需人工确认故障后才可启动自动控制")
|
||||
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制"
|
||||
: runtime?.rem_local ? "设备处于本地模式(REM关),无法启动自动控制"
|
||||
: "需人工确认故障后才可启动自动控制")
|
||||
: (isAutoOn ? "停止自动控制" : "启动自动控制");
|
||||
autoBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
Loading…
Reference in New Issue