Compare commits
2 Commits
45b2317ee8
...
4227747852
| Author | SHA1 | Date |
|---|---|---|
|
|
4227747852 | |
|
|
1354f89204 |
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE point
|
||||||
|
ADD CONSTRAINT uq_equipment_signal_role UNIQUE (equipment_id, signal_role);
|
||||||
|
|
@ -317,6 +317,15 @@ async fn check_fault_comm(
|
||||||
None
|
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);
|
drop(monitor);
|
||||||
|
|
||||||
let prev_comm = runtime.comm_locked;
|
let prev_comm = runtime.comm_locked;
|
||||||
|
|
@ -324,9 +333,11 @@ async fn check_fault_comm(
|
||||||
let prev_fault_locked = runtime.fault_locked;
|
let prev_fault_locked = runtime.fault_locked;
|
||||||
let prev_auto = runtime.auto_enabled;
|
let prev_auto = runtime.auto_enabled;
|
||||||
let prev_ack = runtime.manual_ack_required;
|
let prev_ack = runtime.manual_ack_required;
|
||||||
|
let prev_rem_local = runtime.rem_local;
|
||||||
|
|
||||||
runtime.comm_locked = any_bad;
|
runtime.comm_locked = any_bad;
|
||||||
runtime.flt_active = any_flt;
|
runtime.flt_active = any_flt;
|
||||||
|
runtime.rem_local = any_rem_local;
|
||||||
|
|
||||||
if !prev_comm && runtime.comm_locked {
|
if !prev_comm && runtime.comm_locked {
|
||||||
let _ = state.event_manager.send(AppEvent::CommLocked { unit_id: unit.id });
|
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.comm_locked != prev_comm
|
||||||
|| runtime.flt_active != prev_flt
|
|| runtime.flt_active != prev_flt
|
||||||
|| runtime.fault_locked != prev_fault_locked
|
|| runtime.fault_locked != prev_fault_locked
|
||||||
|| runtime.auto_enabled != prev_auto
|
|| runtime.auto_enabled != prev_auto
|
||||||
|| runtime.manual_ack_required != prev_ack
|
|| runtime.manual_ack_required != prev_ack
|
||||||
|
|| runtime.rem_local != prev_rem_local
|
||||||
}
|
}
|
||||||
|
|
||||||
type EquipMaps = (
|
type EquipMaps = (
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ pub struct UnitRuntime {
|
||||||
pub flt_active: bool,
|
pub flt_active: bool,
|
||||||
pub comm_locked: bool,
|
pub comm_locked: bool,
|
||||||
pub manual_ack_required: 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 {
|
impl UnitRuntime {
|
||||||
|
|
@ -39,6 +41,7 @@ impl UnitRuntime {
|
||||||
flt_active: false,
|
flt_active: false,
|
||||||
comm_locked: false,
|
comm_locked: false,
|
||||||
manual_ack_required: 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 {
|
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)]
|
#[derive(Debug, Deserialize, Validate)]
|
||||||
|
|
@ -543,6 +543,8 @@ pub async fn start_auto_unit(
|
||||||
"Unit is fault locked, cannot start auto control"
|
"Unit is fault locked, cannot start auto control"
|
||||||
} else if runtime.comm_locked {
|
} else if runtime.comm_locked {
|
||||||
"Unit communication is locked, cannot start auto control"
|
"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 {
|
} else {
|
||||||
"Fault acknowledgement required before starting auto control"
|
"Fault acknowledgement required before starting auto control"
|
||||||
};
|
};
|
||||||
|
|
@ -740,6 +742,7 @@ mod tests {
|
||||||
flt_active: false,
|
flt_active: false,
|
||||||
comm_locked: true,
|
comm_locked: true,
|
||||||
manual_ack_required: false,
|
manual_ack_required: false,
|
||||||
|
rem_local: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(auto_control_start_blocked(&runtime));
|
assert!(auto_control_start_blocked(&runtime));
|
||||||
|
|
|
||||||
|
|
@ -208,9 +208,14 @@ export function renderEquipments() {
|
||||||
actionRow.append(editBtn, deleteBtn);
|
actionRow.append(editBtn, deleteBtn);
|
||||||
|
|
||||||
if (equipment.kind === "coal_feeder" || equipment.kind === "distributor") {
|
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");
|
const startBtn = document.createElement("button");
|
||||||
startBtn.className = "secondary";
|
startBtn.className = "secondary";
|
||||||
startBtn.textContent = "Start";
|
startBtn.textContent = "Start";
|
||||||
|
startBtn.disabled = remLocal;
|
||||||
|
startBtn.title = remLocal ? "设备处于本地模式(REM关)" : "";
|
||||||
startBtn.addEventListener("click", (e) => {
|
startBtn.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
apiFetch(`/api/control/equipment/${equipment.id}/start`, { method: "POST" })
|
apiFetch(`/api/control/equipment/${equipment.id}/start`, { method: "POST" })
|
||||||
|
|
@ -220,6 +225,8 @@ export function renderEquipments() {
|
||||||
const stopBtn = document.createElement("button");
|
const stopBtn = document.createElement("button");
|
||||||
stopBtn.className = "danger";
|
stopBtn.className = "danger";
|
||||||
stopBtn.textContent = "Stop";
|
stopBtn.textContent = "Stop";
|
||||||
|
stopBtn.disabled = remLocal;
|
||||||
|
stopBtn.title = remLocal ? "设备处于本地模式(REM关)" : "";
|
||||||
stopBtn.addEventListener("click", (e) => {
|
stopBtn.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
apiFetch(`/api/control/equipment/${equipment.id}/stop`, { method: "POST" })
|
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 actions = item.querySelector(".ops-unit-item-actions");
|
||||||
|
|
||||||
const isAutoOn = runtime?.auto_enabled;
|
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");
|
const autoBtn = document.createElement("button");
|
||||||
autoBtn.className = isAutoOn ? "danger" : "secondary";
|
autoBtn.className = isAutoOn ? "danger" : "secondary";
|
||||||
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
|
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
|
||||||
autoBtn.disabled = startBlocked;
|
autoBtn.disabled = startBlocked;
|
||||||
autoBtn.title = startBlocked
|
autoBtn.title = startBlocked
|
||||||
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制" : "需人工确认故障后才可启动自动控制")
|
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制"
|
||||||
|
: runtime?.rem_local ? "设备处于本地模式(REM关),无法启动自动控制"
|
||||||
|
: "需人工确认故障后才可启动自动控制")
|
||||||
: (isAutoOn ? "停止自动控制" : "启动自动控制");
|
: (isAutoOn ? "停止自动控制" : "启动自动控制");
|
||||||
autoBtn.addEventListener("click", (e) => {
|
autoBtn.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
||||||
|
|
@ -151,13 +151,15 @@ export function renderUnits() {
|
||||||
actions.append(editBtn, deleteBtn);
|
actions.append(editBtn, deleteBtn);
|
||||||
|
|
||||||
const isAutoOn = runtime?.auto_enabled;
|
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");
|
const autoBtn = document.createElement("button");
|
||||||
autoBtn.className = isAutoOn ? "danger" : "secondary";
|
autoBtn.className = isAutoOn ? "danger" : "secondary";
|
||||||
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
|
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
|
||||||
autoBtn.disabled = startBlocked;
|
autoBtn.disabled = startBlocked;
|
||||||
autoBtn.title = startBlocked
|
autoBtn.title = startBlocked
|
||||||
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制" : "需人工确认故障后才可启动自动控制")
|
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制"
|
||||||
|
: runtime?.rem_local ? "设备处于本地模式(REM关),无法启动自动控制"
|
||||||
|
: "需人工确认故障后才可启动自动控制")
|
||||||
: (isAutoOn ? "停止自动控制" : "启动自动控制");
|
: (isAutoOn ? "停止自动控制" : "启动自动控制");
|
||||||
autoBtn.addEventListener("click", (e) => {
|
autoBtn.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue