Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e233ae718 | ||
|
|
f35ba7862e | ||
|
|
80048c0bbb | ||
|
|
b8bb8053c7 | ||
|
|
0305867394 | ||
|
|
09f56ed990 | ||
|
|
8beb24cef0 | ||
|
|
ed84982c8e | ||
|
|
e792ed1b4d | ||
|
|
132ee383ee | ||
|
|
34a8c54098 |
No files matched your search
@@ -16,7 +16,9 @@ type Props = {
|
||||
min: number;
|
||||
max: number;
|
||||
onChangeTargetTemp?: (temperature: number) => void;
|
||||
targetTemperatureEnabled: boolean;
|
||||
heatingFault?: boolean;
|
||||
onRetryHeating?: () => void;
|
||||
targetTemperatureEnabled?: boolean;
|
||||
};
|
||||
export function HeatingZone({
|
||||
title,
|
||||
@@ -26,7 +28,9 @@ export function HeatingZone({
|
||||
heatingTimeSeries,
|
||||
heatingPower,
|
||||
onChangeTargetTemp,
|
||||
targetTemperatureEnabled,
|
||||
heatingFault,
|
||||
onRetryHeating,
|
||||
targetTemperatureEnabled = true,
|
||||
}: Props) {
|
||||
const targetTemperature = heatingState?.target_temperature ?? 150;
|
||||
|
||||
@@ -73,6 +77,23 @@ export function HeatingZone({
|
||||
Cant Read Temperature! Check Temperature Sensor Wiring!
|
||||
</StatusBadge>
|
||||
)}
|
||||
|
||||
{heatingFault && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<StatusBadge variant="error">
|
||||
Heating Fault: Temperature not increasing. Extruder set to standby.
|
||||
</StatusBadge>
|
||||
{onRetryHeating && (
|
||||
<button
|
||||
onClick={onRetryHeating}
|
||||
className="inline-block w-fit max-w-max rounded bg-blue-600 px-4 py-2 text-base whitespace-nowrap text-white hover:bg-blue-700"
|
||||
style={{ minWidth: "auto", width: "fit-content" }}
|
||||
>
|
||||
Retry Heating
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ControlCard>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { roundToDecimals } from "@/lib/decimal";
|
||||
import { useExtruder3 } from "./useExtruder";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { StatusBadge } from "@/control/StatusBadge";
|
||||
import { GlobalHeatingFaultToastManager } from "./GlobalHeatingFaultToastManager";
|
||||
|
||||
export function Extruder3ControlPage() {
|
||||
const {
|
||||
@@ -45,8 +46,17 @@ export function Extruder3ControlPage() {
|
||||
|
||||
isLoading,
|
||||
isDisabled,
|
||||
retryHeating,
|
||||
acknowledgeHeatingFault,
|
||||
} = useExtruder3();
|
||||
|
||||
// Helper to check if a zone has a heating fault
|
||||
const getZoneFault = (zone: "front" | "middle" | "back" | "nozzle") => {
|
||||
const faultZone = state?.heating_fault_state?.fault_zone;
|
||||
const faultAcknowledged = state?.heating_fault_state?.fault_acknowledged;
|
||||
return faultZone === zone && !faultAcknowledged;
|
||||
};
|
||||
|
||||
function isZoneReadyForExtrusion(
|
||||
temperature: number,
|
||||
targetTemperature: number,
|
||||
@@ -80,6 +90,10 @@ export function Extruder3ControlPage() {
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<GlobalHeatingFaultToastManager
|
||||
state={state}
|
||||
onAcknowledgeHeatingFault={acknowledgeHeatingFault}
|
||||
/>
|
||||
<ControlGrid>
|
||||
<HeatingZone
|
||||
title={"Heating Front"}
|
||||
@@ -89,7 +103,9 @@ export function Extruder3ControlPage() {
|
||||
onChangeTargetTemp={setFrontHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
targetTemperatureEnabled={true}
|
||||
heatingFault={getZoneFault("front")}
|
||||
onRetryHeating={retryHeating}
|
||||
targetTemperatureEnabled
|
||||
/>
|
||||
<HeatingZone
|
||||
title={"Heating Middle"}
|
||||
@@ -99,7 +115,9 @@ export function Extruder3ControlPage() {
|
||||
onChangeTargetTemp={setMiddleHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
targetTemperatureEnabled={true}
|
||||
heatingFault={getZoneFault("middle")}
|
||||
onRetryHeating={retryHeating}
|
||||
targetTemperatureEnabled
|
||||
/>
|
||||
<HeatingZone
|
||||
title={"Heating Back"}
|
||||
@@ -109,7 +127,9 @@ export function Extruder3ControlPage() {
|
||||
onChangeTargetTemp={setBackHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
targetTemperatureEnabled={true}
|
||||
heatingFault={getZoneFault("back")}
|
||||
onRetryHeating={retryHeating}
|
||||
targetTemperatureEnabled
|
||||
/>
|
||||
<HeatingZone
|
||||
title={"Heating Nozzle"}
|
||||
@@ -119,6 +139,8 @@ export function Extruder3ControlPage() {
|
||||
onChangeTargetTemp={setNozzleHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
heatingFault={getZoneFault("nozzle")}
|
||||
onRetryHeating={retryHeating}
|
||||
targetTemperatureEnabled={
|
||||
state?.extruder_settings_state.nozzle_temperature_target_enabled ??
|
||||
true
|
||||
|
||||
@@ -16,11 +16,12 @@ export function Extruder3SettingsPage() {
|
||||
resetInverter,
|
||||
setExtruderPressureLimit,
|
||||
setExtruderPressureLimitEnabled,
|
||||
setHeatingSafeguardEnabled,
|
||||
setTemperatureTargetEnabled,
|
||||
setPressurePidKp,
|
||||
setPressurePidKi,
|
||||
setPressurePidKd,
|
||||
setTemperaturePidValue,
|
||||
setTemperatureTargetEnabled,
|
||||
} = useExtruder3();
|
||||
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
@@ -69,10 +70,26 @@ export function Extruder3SettingsPage() {
|
||||
onChange={setExtruderPressureLimitEnabled}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Heating Safeguard">
|
||||
<SelectionGroupBoolean
|
||||
value={
|
||||
state?.extruder_settings_state.heating_safeguard_enabled ?? true
|
||||
}
|
||||
optionTrue={{ children: "Enabled" }}
|
||||
optionFalse={{ children: "Disabled" }}
|
||||
onChange={setHeatingSafeguardEnabled}
|
||||
/>
|
||||
<p className="text-muted-foreground mt-1 text-sm">
|
||||
When enabled, monitors heating progress and automatically sets to
|
||||
standby if temperature doesn't increase by at least 5°C within
|
||||
60 seconds.
|
||||
</p>
|
||||
</Label>
|
||||
<Label label="Nozzle Temperature Target Enabled">
|
||||
<SelectionGroupBoolean
|
||||
value={
|
||||
state?.extruder_settings_state.nozzle_temperature_target_enabled
|
||||
state?.extruder_settings_state
|
||||
.nozzle_temperature_target_enabled ?? true
|
||||
}
|
||||
optionTrue={{ children: "Enabled" }}
|
||||
optionFalse={{ children: "Disabled" }}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Icon } from "@/components/Icon";
|
||||
|
||||
type HeatingFaultState = {
|
||||
heating_fault_state?: {
|
||||
fault_zone?: string | null;
|
||||
fault_acknowledged?: boolean;
|
||||
};
|
||||
is_default_state?: boolean;
|
||||
};
|
||||
|
||||
type HeatingFaultToastProps = {
|
||||
zoneName: string;
|
||||
onAcknowledge: () => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
function HeatingFaultToast({
|
||||
zoneName,
|
||||
onAcknowledge,
|
||||
children,
|
||||
}: HeatingFaultToastProps) {
|
||||
return (
|
||||
<div className="flex w-100 flex-col gap-3 rounded-xl border border-red-400 bg-red-600 p-4 text-white shadow-xl backdrop-blur-sm transition-all duration-300">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon name="lu:TriangleAlert" className="h-5 w-5 text-yellow-200" />
|
||||
<strong className="text-lg font-semibold tracking-wide">
|
||||
Heating Fault
|
||||
</strong>
|
||||
</div>
|
||||
<button
|
||||
className="rounded-md p-1 text-2xl font-bold text-white/80 hover:bg-red-500 hover:text-white focus:ring-2 focus:ring-white/30 focus:outline-none"
|
||||
onClick={onAcknowledge}
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{children ?? (
|
||||
<p className="text-base leading-snug text-red-50">
|
||||
The <strong>{zoneName}</strong> heating zone did not increase in
|
||||
temperature by at least 5°C within 60 seconds. The extruder has been
|
||||
automatically set to standby mode.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function toZoneName(faultZone: string | null): string {
|
||||
switch (faultZone) {
|
||||
case "front":
|
||||
return "Front";
|
||||
case "middle":
|
||||
return "Middle";
|
||||
case "back":
|
||||
return "Back";
|
||||
case "nozzle":
|
||||
return "Nozzle";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
function useHeatingFaultToastEffect({
|
||||
state,
|
||||
onAcknowledgeHeatingFault,
|
||||
}: {
|
||||
state: HeatingFaultState | null | undefined;
|
||||
onAcknowledgeHeatingFault: () => void;
|
||||
}) {
|
||||
// Deduplicate by active fault zone
|
||||
const activeFaultZone = useRef<string | null>(null);
|
||||
const currentToastId = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!state) return;
|
||||
const faultZone = state.heating_fault_state?.fault_zone ?? null;
|
||||
const faultAcknowledged =
|
||||
state.heating_fault_state?.fault_acknowledged ?? false;
|
||||
const isDefault = !!state.is_default_state;
|
||||
|
||||
if (isDefault) return;
|
||||
|
||||
const faultZoneStr = faultZone ?? "unknown";
|
||||
const zoneName = toZoneName(faultZone);
|
||||
|
||||
if (
|
||||
faultZone &&
|
||||
!faultAcknowledged &&
|
||||
activeFaultZone.current !== faultZone
|
||||
) {
|
||||
activeFaultZone.current = faultZone;
|
||||
const toastId = `heating-fault-${faultZoneStr}`;
|
||||
currentToastId.current = toastId;
|
||||
|
||||
toast(
|
||||
<HeatingFaultToast
|
||||
zoneName={zoneName}
|
||||
onAcknowledge={() => {
|
||||
onAcknowledgeHeatingFault();
|
||||
toast.dismiss(toastId);
|
||||
activeFaultZone.current = null;
|
||||
currentToastId.current = null;
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
id: toastId,
|
||||
duration: Infinity,
|
||||
position: "top-center",
|
||||
style: {
|
||||
background: "transparent",
|
||||
padding: 0,
|
||||
boxShadow: "none",
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (faultAcknowledged || !faultZone) {
|
||||
if (currentToastId.current) {
|
||||
toast.dismiss(currentToastId.current);
|
||||
currentToastId.current = null;
|
||||
}
|
||||
activeFaultZone.current = null;
|
||||
}
|
||||
}, [state, onAcknowledgeHeatingFault]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches StateEvent for heating faults and shows toasts.
|
||||
* Component is non-visual.
|
||||
*/
|
||||
export function GlobalHeatingFaultToastManager({
|
||||
state,
|
||||
onAcknowledgeHeatingFault,
|
||||
}: {
|
||||
state:
|
||||
| {
|
||||
heating_fault_state?: {
|
||||
fault_zone?: string | null;
|
||||
fault_acknowledged?: boolean;
|
||||
};
|
||||
is_default_state?: boolean;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
onAcknowledgeHeatingFault: () => void;
|
||||
}) {
|
||||
useHeatingFaultToastEffect({ state, onAcknowledgeHeatingFault });
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -103,13 +103,22 @@ export const heatingStatesSchema = z.object({
|
||||
middle: heatingStateSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Heating fault state schema
|
||||
*/
|
||||
export const heatingFaultStateSchema = z.object({
|
||||
fault_zone: z.string().nullable(),
|
||||
fault_acknowledged: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Extruder settings state schema
|
||||
*/
|
||||
export const extruderSettingsStateSchema = z.object({
|
||||
pressure_limit: z.number(),
|
||||
pressure_limit_enabled: z.boolean(),
|
||||
nozzle_temperature_target_enabled: z.boolean(),
|
||||
heating_safeguard_enabled: z.boolean(),
|
||||
nozzle_temperature_target_enabled: z.boolean().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -178,6 +187,7 @@ export const stateEventDataSchema = z.object({
|
||||
extruder_settings_state: extruderSettingsStateSchema,
|
||||
inverter_status_state: inverterStatusStateSchema,
|
||||
pid_settings: pidSettingsSchema,
|
||||
heating_fault_state: heatingFaultStateSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
@@ -300,7 +310,6 @@ export function extruder3MessageHandler(
|
||||
|
||||
try {
|
||||
if (eventName === "StateEvent") {
|
||||
console.log(event);
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
const timestamp = event.ts;
|
||||
const nextTargetPressure = stateEvent.data.pressure_state.target_bar;
|
||||
|
||||
@@ -232,18 +232,39 @@ export function useExtruder3() {
|
||||
);
|
||||
};
|
||||
|
||||
const { request: requestHeatingSafeguardEnabled } = useMachineMutation(
|
||||
z.object({ SetHeatingSafeguardEnabled: z.boolean() }),
|
||||
);
|
||||
|
||||
const setHeatingSafeguardEnabled = (enabled: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.extruder_settings_state.heating_safeguard_enabled =
|
||||
enabled;
|
||||
},
|
||||
() =>
|
||||
requestHeatingSafeguardEnabled({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetHeatingSafeguardEnabled: enabled },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setTemperatureTargetEnabled = (enabled: boolean) => {
|
||||
const res = enabled
|
||||
? true
|
||||
: confirm(
|
||||
"This will disable the ability to set a temperature target for the nozzle heating element and will any wiring error message. Only proceed if you understand the risks.",
|
||||
"This will disable the ability to set a temperature target for the nozzle heating element. Only proceed if you understand the risks.",
|
||||
);
|
||||
|
||||
if (res) {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.extruder_settings_state.nozzle_temperature_target_enabled =
|
||||
enabled;
|
||||
(
|
||||
current.data.extruder_settings_state as {
|
||||
nozzle_temperature_target_enabled?: boolean;
|
||||
}
|
||||
).nozzle_temperature_target_enabled = enabled;
|
||||
},
|
||||
() =>
|
||||
requestNozzleTemperatureTargetEnabled({
|
||||
@@ -432,6 +453,28 @@ export function useExtruder3() {
|
||||
z.object({ ResetInverter: z.boolean() }),
|
||||
);
|
||||
|
||||
const { request: requestRetryHeating } = useMachineMutation(
|
||||
z.object({ RetryHeating: z.literal(true) }),
|
||||
);
|
||||
|
||||
const retryHeating = () => {
|
||||
requestRetryHeating({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { RetryHeating: true },
|
||||
});
|
||||
};
|
||||
|
||||
const { request: requestAcknowledgeHeatingFault } = useMachineMutation(
|
||||
z.object({ AcknowledgeHeatingFault: z.literal(true) }),
|
||||
);
|
||||
|
||||
const acknowledgeHeatingFault = () => {
|
||||
requestAcknowledgeHeatingFault({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { AcknowledgeHeatingFault: true },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
// Consolidated state
|
||||
state: stateOptimistic.value?.data,
|
||||
@@ -481,11 +524,14 @@ export function useExtruder3() {
|
||||
setMiddleHeatingTemperature,
|
||||
setExtruderPressureLimit,
|
||||
setExtruderPressureLimitEnabled,
|
||||
setHeatingSafeguardEnabled,
|
||||
setTemperatureTargetEnabled,
|
||||
setPressurePidKp,
|
||||
setPressurePidKi,
|
||||
setPressurePidKd,
|
||||
setTemperaturePidValue,
|
||||
resetInverter,
|
||||
retryHeating,
|
||||
acknowledgeHeatingFault,
|
||||
};
|
||||
}
|
||||
@@ -152,6 +152,8 @@ pub struct HeatingState {
|
||||
pub struct ExtruderSettingsState {
|
||||
pub pressure_limit: f64,
|
||||
pub pressure_limit_enabled: bool,
|
||||
/// Heating safeguard watchdog enabled (default: true)
|
||||
pub heating_safeguard_enabled: bool,
|
||||
pub nozzle_temperature_target_enabled: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ impl ExtruderV2 {
|
||||
pressure_limit_enabled: self
|
||||
.screw_speed_controller
|
||||
.get_nozzle_pressure_limit_enabled(),
|
||||
// For ExtruderV2 the heating safeguard is always enabled and not user-configurable
|
||||
heating_safeguard_enabled: true,
|
||||
nozzle_temperature_target_enabled: self
|
||||
.temperature_controller_nozzle
|
||||
.get_temperature_target_enabled(),
|
||||
@@ -215,7 +217,7 @@ impl ExtruderV2 {
|
||||
self.namespace.emit(ExtruderV2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
// === Steuerungsfunktionen mit emit_state ===
|
||||
// === Control functions with emit_state ===
|
||||
|
||||
pub fn set_nozzle_pressure_limit_is_enabled(&mut self, enabled: bool) {
|
||||
self.screw_speed_controller
|
||||
|
||||
@@ -81,6 +81,7 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
extruder_settings_state: ExtruderSettingsState {
|
||||
pressure_limit: 200.0,
|
||||
pressure_limit_enabled: false,
|
||||
heating_safeguard_enabled: true,
|
||||
nozzle_temperature_target_enabled: true,
|
||||
},
|
||||
inverter_status_state: InverterStatusState {
|
||||
|
||||
@@ -166,10 +166,10 @@ impl ExtruderV2 {
|
||||
self.last_energy_calculation_time = Some(now);
|
||||
}
|
||||
|
||||
// Funktionen ohne emit_state bleiben hier
|
||||
// Functions without emit_state remain here
|
||||
|
||||
// Set all relais to ZERO
|
||||
// We dont need a function to enable again though, as the act Loop will detect the mode
|
||||
// Set all relays to ZERO
|
||||
// We don't need a function to enable again though, as the act loop will detect the mode
|
||||
fn turn_heating_off(&mut self) {
|
||||
self.temperature_controller_back.disable();
|
||||
self.temperature_controller_front.disable();
|
||||
|
||||
@@ -140,14 +140,14 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
let t3 = TemperatureInput::new(el3204.clone(), EL3204Port::T3);
|
||||
let t4 = TemperatureInput::new(el3204, EL3204Port::T4);
|
||||
|
||||
// For the Relais
|
||||
// For the Relays
|
||||
let digital_out_1 = DigitalOutput::new(el2004.clone(), EL2004Port::DO1);
|
||||
let digital_out_2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
|
||||
let digital_out_3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
|
||||
let digital_out_4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
|
||||
|
||||
let pressure_sensor = AnalogInput::new(el3021, EL3021Port::AI1);
|
||||
// The Extruders temparature Controllers should disable the relais when the max_temperature is reached
|
||||
// The Extruder's temperature controllers should disable the relays when the max_temperature is reached
|
||||
let extruder_max_temperature = ThermodynamicTemperature::new::<degree_celsius>(300.0);
|
||||
// Only front heating on: These values work 0.08, 0.001, 0.007, Overshoot 0.5 undershoot ~0.7 (Problems when starting far away because of integral)
|
||||
let temperature_controller_front = TemperatureController::new(
|
||||
|
||||
@@ -20,7 +20,8 @@ pub struct TemperatureController {
|
||||
temperature_pid_output: f64,
|
||||
heating_element_wattage: f64,
|
||||
max_clamp: f64,
|
||||
target_temp_enabled: bool, // Sets whether the frontend should display a target temperature setter for this temp controller
|
||||
/// When false, target temperature cannot be set (nozzle only; other zones always true)
|
||||
temperature_target_enabled: bool,
|
||||
}
|
||||
|
||||
impl TemperatureController {
|
||||
@@ -56,22 +57,22 @@ impl TemperatureController {
|
||||
temperature_pid_output: 0.0,
|
||||
heating_element_wattage,
|
||||
max_clamp,
|
||||
target_temp_enabled: true,
|
||||
temperature_target_enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_temperature_target_enabled(&self) -> bool {
|
||||
self.temperature_target_enabled
|
||||
}
|
||||
|
||||
pub fn set_temperature_target_enabled(&mut self, enabled: bool) {
|
||||
self.temperature_target_enabled = enabled;
|
||||
}
|
||||
|
||||
pub fn set_target_temperature(&mut self, temp: ThermodynamicTemperature) {
|
||||
self.heating.target_temperature = temp;
|
||||
}
|
||||
|
||||
pub fn set_temperature_target_enabled(&mut self, enabled: bool) {
|
||||
self.target_temp_enabled = enabled;
|
||||
}
|
||||
|
||||
pub fn get_temperature_target_enabled(&self) -> bool {
|
||||
self.target_temp_enabled
|
||||
}
|
||||
|
||||
pub const fn disallow_heating(&mut self) {
|
||||
self.heating_allowed = false;
|
||||
}
|
||||
@@ -96,7 +97,7 @@ impl TemperatureController {
|
||||
self.heating.temperature = temperature_celsius;
|
||||
|
||||
if self.heating.temperature > self.max_temperature {
|
||||
// disable the relais and return
|
||||
// Disable the relays and return
|
||||
self.relais.set(false);
|
||||
self.heating.heating = false;
|
||||
return;
|
||||
|
||||
@@ -41,7 +41,10 @@ impl MachineAct for ExtruderV3 {
|
||||
self.switch_to_heat();
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
// Monitor heating watchdog if safeguard is enabled
|
||||
if self.heating_safeguard_enabled {
|
||||
self.update_heating_watchdog(now);
|
||||
}
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
|
||||
@@ -110,6 +110,8 @@ pub struct StateEvent {
|
||||
pub inverter_status_state: InverterStatusState,
|
||||
/// pid settings
|
||||
pub pid_settings: PidSettingsStates,
|
||||
/// heating fault state
|
||||
pub heating_fault_state: HeatingFaultState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
@@ -117,6 +119,15 @@ pub struct ModeState {
|
||||
pub mode: ExtruderV3Mode,
|
||||
}
|
||||
|
||||
/// Heating fault information
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HeatingFaultState {
|
||||
/// Zone that has a heating fault (None if no fault)
|
||||
pub fault_zone: Option<String>,
|
||||
/// Whether a fault has been detected and acknowledged
|
||||
pub fault_acknowledged: bool,
|
||||
}
|
||||
|
||||
pub enum ExtruderV3Events {
|
||||
LiveValues(Event<LiveValuesEvent>),
|
||||
State(Event<StateEvent>),
|
||||
@@ -143,6 +154,9 @@ pub enum Mutation {
|
||||
SetExtruderPressureLimit(f64),
|
||||
SetExtruderPressureLimitIsEnabled(bool),
|
||||
|
||||
// Heating Safeguard
|
||||
SetHeatingSafeguardEnabled(bool),
|
||||
|
||||
// ToggleTemperatureTarget
|
||||
SetNozzleTemperatureTargetEnabled(bool),
|
||||
|
||||
@@ -152,6 +166,10 @@ pub enum Mutation {
|
||||
|
||||
// Reset
|
||||
ResetInverter(bool),
|
||||
|
||||
// Heating Fault Handling
|
||||
RetryHeating,
|
||||
AcknowledgeHeatingFault,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -216,7 +234,12 @@ impl MachineApi for ExtruderV3 {
|
||||
self.set_target_temperature(temp, HeatingType::Back)
|
||||
}
|
||||
Mutation::SetNozzleHeatingTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Nozzle)
|
||||
if self
|
||||
.temperature_controller_nozzle
|
||||
.get_temperature_target_enabled()
|
||||
{
|
||||
self.set_target_temperature(temp, HeatingType::Nozzle)
|
||||
}
|
||||
}
|
||||
Mutation::SetExtruderPressureLimit(pressure_limit) => {
|
||||
self.set_nozzle_pressure_limit(pressure_limit);
|
||||
@@ -224,14 +247,23 @@ impl MachineApi for ExtruderV3 {
|
||||
Mutation::SetExtruderPressureLimitIsEnabled(enabled) => {
|
||||
self.set_nozzle_pressure_limit_is_enabled(enabled);
|
||||
}
|
||||
Mutation::SetHeatingSafeguardEnabled(enabled) => {
|
||||
self.set_heating_safeguard_enabled(enabled);
|
||||
}
|
||||
Mutation::SetNozzleTemperatureTargetEnabled(enabled) => {
|
||||
self.set_nozzle_temperature_target_is_enabled(enabled);
|
||||
}
|
||||
Mutation::SetPressurePidSettings(settings) => {
|
||||
self.configure_pressure_pid(settings);
|
||||
}
|
||||
Mutation::SetTemperaturePidSettings(settings) => {
|
||||
self.configure_temperature_pid(settings);
|
||||
}
|
||||
Mutation::SetNozzleTemperatureTargetEnabled(enabled) => {
|
||||
self.set_nozzle_temperature_target_is_enabled(enabled);
|
||||
Mutation::RetryHeating => {
|
||||
self.retry_heating();
|
||||
}
|
||||
Mutation::AcknowledgeHeatingFault => {
|
||||
self.acknowledge_heating_fault();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -102,6 +102,7 @@ impl ExtruderV3 {
|
||||
pressure_limit_enabled: self
|
||||
.screw_speed_controller
|
||||
.get_nozzle_pressure_limit_enabled(),
|
||||
heating_safeguard_enabled: self.heating_safeguard_enabled,
|
||||
nozzle_temperature_target_enabled: self
|
||||
.temperature_controller_nozzle
|
||||
.get_temperature_target_enabled(),
|
||||
@@ -150,6 +151,7 @@ impl ExtruderV3 {
|
||||
kd: self.screw_speed_controller.pid.get_kd(),
|
||||
},
|
||||
},
|
||||
heating_fault_state: self.heating_fault_state.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,7 +237,7 @@ impl ExtruderV3 {
|
||||
self.namespace.emit(ExtruderV3Events::LiveValues(event));
|
||||
}
|
||||
|
||||
// === Steuerungsfunktionen mit emit_state ===
|
||||
// === Control functions with emit_state ===
|
||||
|
||||
pub fn set_nozzle_pressure_limit_is_enabled(&mut self, enabled: bool) {
|
||||
self.screw_speed_controller
|
||||
@@ -243,9 +245,41 @@ impl ExtruderV3 {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_heating_safeguard_enabled(&mut self, enabled: bool) {
|
||||
self.heating_safeguard_enabled = enabled;
|
||||
tracing::info!(
|
||||
"Heating safeguard {}",
|
||||
if enabled { "enabled" } else { "disabled" }
|
||||
);
|
||||
// Reset watchdog state when toggling
|
||||
self.heating_watchdog = super::HeatingWatchdog::default();
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_nozzle_temperature_target_is_enabled(&mut self, enabled: bool) {
|
||||
self.temperature_controller_nozzle
|
||||
.set_temperature_target_enabled(enabled);
|
||||
tracing::info!(
|
||||
"Nozzle temperature target {}",
|
||||
if enabled { "enabled" } else { "disabled" }
|
||||
);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn retry_heating(&mut self) {
|
||||
tracing::info!(
|
||||
"Retrying heating after fault - resetting watchdog and switching to Heat mode"
|
||||
);
|
||||
self.heating_fault_state.fault_zone = None;
|
||||
self.heating_fault_state.fault_acknowledged = false;
|
||||
self.heating_watchdog = super::HeatingWatchdog::default();
|
||||
self.switch_to_heat();
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn acknowledge_heating_fault(&mut self) {
|
||||
tracing::info!("Heating fault acknowledged by user");
|
||||
self.heating_fault_state.fault_acknowledged = true;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
use super::ExtruderV2;
|
||||
use crate::{
|
||||
MachineApi,
|
||||
extruder1::{HeatingType, api::Mutation},
|
||||
extruder1::{ExtruderV2Mode, HeatingType},
|
||||
extruder2::ExtruderV3Mode,
|
||||
extruder2::api::Mutation,
|
||||
};
|
||||
|
||||
fn map_v3_mode_to_v2(mode: ExtruderV3Mode) -> ExtruderV2Mode {
|
||||
match mode {
|
||||
ExtruderV3Mode::Standby => ExtruderV2Mode::Standby,
|
||||
ExtruderV3Mode::Heat => ExtruderV2Mode::Heat,
|
||||
ExtruderV3Mode::Extrude => ExtruderV2Mode::Extrude,
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for ExtruderV2 {
|
||||
fn api_mutate(&mut self, request_body: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||
// there are multiple Modbus Frames that are "prebuilt"
|
||||
let control: Mutation = serde_json::from_value(request_body)?;
|
||||
match control {
|
||||
Mutation::SetExtruderMode(mode) => self.set_mode_state(mode),
|
||||
Mutation::SetExtruderMode(mode) => self.set_mode_state(map_v3_mode_to_v2(mode)),
|
||||
Mutation::SetInverterRotationDirection(forward) => self.set_rotation_state(forward),
|
||||
Mutation::SetInverterRegulation(uses_rpm) => self.set_regulation(uses_rpm),
|
||||
Mutation::SetInverterTargetPressure(bar) => self.set_target_pressure(bar),
|
||||
@@ -41,9 +51,18 @@ impl MachineApi for ExtruderV2 {
|
||||
Mutation::SetTemperaturePidSettings(settings) => {
|
||||
self.configure_temperature_pid(settings);
|
||||
}
|
||||
Mutation::SetHeatingSafeguardEnabled(_enabled) => {
|
||||
// Mock implementation - just acknowledge
|
||||
}
|
||||
Mutation::SetNozzleTemperatureTargetEnabled(enabled) => {
|
||||
self.set_nozzle_temperature_target_is_enabled(enabled);
|
||||
}
|
||||
Mutation::RetryHeating => {
|
||||
// Mock implementation - just acknowledge
|
||||
}
|
||||
Mutation::AcknowledgeHeatingFault => {
|
||||
// Mock implementation - just acknowledge
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use crate::extruder1::{
|
||||
ExtruderV2Mode, HeatingType,
|
||||
api::{ExtruderV2Events, LiveValuesEvent, ModeState, PidSettings, StateEvent, TemperaturePid},
|
||||
api::{ModeState as ModeStateV2, PidSettings, TemperaturePid},
|
||||
};
|
||||
use crate::extruder2::ExtruderV3Mode;
|
||||
use crate::extruder2::api::{
|
||||
ExtruderV3Events, HeatingFaultState, LiveValuesEvent, ModeState, MotorStatusValues, StateEvent,
|
||||
};
|
||||
use crate::extruder2::mock::ExtruderV2;
|
||||
|
||||
@@ -14,7 +18,13 @@ impl ExtruderV2 {
|
||||
StateEvent {
|
||||
is_default_state: !self.emitted_default_state,
|
||||
rotation_state: self.rotation_state.clone(),
|
||||
mode_state: self.mode_state.clone(),
|
||||
mode_state: ModeState {
|
||||
mode: match self.mode_state.mode {
|
||||
ExtruderV2Mode::Standby => ExtruderV3Mode::Standby,
|
||||
ExtruderV2Mode::Heat => ExtruderV3Mode::Heat,
|
||||
ExtruderV2Mode::Extrude => ExtruderV3Mode::Extrude,
|
||||
},
|
||||
},
|
||||
regulation_state: self.regulation_state.clone(),
|
||||
pressure_state: self.pressure_state.clone(),
|
||||
screw_state: self.screw_state.clone(),
|
||||
@@ -22,6 +32,10 @@ impl ExtruderV2 {
|
||||
extruder_settings_state: self.extruder_settings_state.clone(),
|
||||
inverter_status_state: self.inverter_status_state.clone(),
|
||||
pid_settings: self.pid_settings.clone(),
|
||||
heating_fault_state: HeatingFaultState {
|
||||
fault_zone: None,
|
||||
fault_acknowledged: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +45,7 @@ impl ExtruderV2 {
|
||||
self.last_status_hash = Some(hash);
|
||||
self.emitted_default_state = true;
|
||||
let event = state.build();
|
||||
self.namespace.emit(ExtruderV2Events::State(event));
|
||||
self.namespace.emit(ExtruderV3Events::State(event));
|
||||
}
|
||||
|
||||
pub fn maybe_emit_state_event(&mut self) {
|
||||
@@ -51,7 +65,13 @@ impl ExtruderV2 {
|
||||
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent {
|
||||
LiveValuesEvent {
|
||||
motor_status: self.motor_status.clone(),
|
||||
motor_status: MotorStatusValues {
|
||||
screw_rpm: self.motor_status.screw_rpm,
|
||||
frequency: self.motor_status.frequency,
|
||||
voltage: self.motor_status.voltage,
|
||||
current: self.motor_status.current,
|
||||
power: self.motor_status.power,
|
||||
},
|
||||
pressure: self.pressure,
|
||||
nozzle_temperature: self.nozzle_temperature,
|
||||
front_temperature: self.front_temperature,
|
||||
@@ -68,7 +88,7 @@ impl ExtruderV2 {
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let event = self.get_live_values().build();
|
||||
self.namespace.emit(ExtruderV2Events::LiveValues(event));
|
||||
self.namespace.emit(ExtruderV3Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn set_nozzle_pressure_limit_is_enabled(&mut self, enabled: bool) {
|
||||
@@ -76,6 +96,12 @@ impl ExtruderV2 {
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_nozzle_temperature_target_enabled(&mut self, enabled: bool) {
|
||||
self.extruder_settings_state
|
||||
.nozzle_temperature_target_enabled = enabled;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_nozzle_pressure_limit(&mut self, pressure: f64) {
|
||||
self.extruder_settings_state.pressure_limit = pressure;
|
||||
self.emit_state();
|
||||
@@ -95,7 +121,7 @@ impl ExtruderV2 {
|
||||
}
|
||||
|
||||
pub fn set_mode_state(&mut self, mode: ExtruderV2Mode) {
|
||||
self.mode_state = ModeState { mode };
|
||||
self.mode_state = ModeStateV2 { mode };
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,12 @@ use crate::{
|
||||
extruder1::{
|
||||
ExtruderV2Mode,
|
||||
api::{
|
||||
ExtruderSettingsState, ExtruderV2Namespace, HeatingStates, InverterStatusState,
|
||||
ModeState, MotorStatusValues, PidSettingsStates, PressureState, RegulationState,
|
||||
RotationState, ScrewState,
|
||||
ExtruderSettingsState, HeatingStates, InverterStatusState, ModeState,
|
||||
MotorStatusValues, PidSettingsStates, PressureState, RegulationState, RotationState,
|
||||
ScrewState,
|
||||
},
|
||||
},
|
||||
extruder2::api::ExtruderV3Namespace,
|
||||
};
|
||||
|
||||
// Just checking mock-machine feature here to exclude these modules from compilation entirely
|
||||
@@ -57,7 +58,7 @@ pub struct ExtruderV2 {
|
||||
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
namespace: ExtruderV2Namespace,
|
||||
namespace: ExtruderV3Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
last_status_hash: Option<u64>,
|
||||
mode: ExtruderV2Mode,
|
||||
|
||||
@@ -3,13 +3,12 @@ use crate::{
|
||||
extruder1::{
|
||||
ExtruderV2Mode,
|
||||
api::{
|
||||
ExtruderSettingsState, ExtruderV2Namespace, HeatingState, HeatingStates,
|
||||
InverterStatusState, ModeState, MotorStatusValues, PidSettings, PidSettingsStates,
|
||||
PressureState, RegulationState, RotationState, ScrewState, TemperaturePid,
|
||||
TemperaturePidStates,
|
||||
ExtruderSettingsState, HeatingState, HeatingStates, InverterStatusState, ModeState,
|
||||
MotorStatusValues, PidSettings, PidSettingsStates, PressureState, RegulationState,
|
||||
RotationState, ScrewState, TemperaturePid, TemperaturePidStates,
|
||||
},
|
||||
},
|
||||
extruder2::mock::ExtruderV2,
|
||||
extruder2::{api::ExtruderV3Namespace, mock::ExtruderV2},
|
||||
};
|
||||
|
||||
impl MachineNewTrait for ExtruderV2 {
|
||||
@@ -39,7 +38,7 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: ExtruderV2Namespace {
|
||||
namespace: ExtruderV3Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: now,
|
||||
@@ -81,6 +80,7 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
extruder_settings_state: ExtruderSettingsState {
|
||||
pressure_limit: 200.0,
|
||||
pressure_limit_enabled: false,
|
||||
heating_safeguard_enabled: true,
|
||||
nozzle_temperature_target_enabled: true,
|
||||
},
|
||||
inverter_status_state: InverterStatusState {
|
||||
|
||||
@@ -52,6 +52,151 @@ pub enum HeatingType {
|
||||
Middle,
|
||||
}
|
||||
|
||||
/// Watchdog state for monitoring heating progress
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeatingWatchdogZone {
|
||||
/// Temperature when heating started for this zone
|
||||
start_temperature: Option<f64>,
|
||||
/// Timestamp when heating started
|
||||
heating_start_time: Option<Instant>,
|
||||
/// Whether a fault has been detected
|
||||
fault_detected: bool,
|
||||
/// Last second that was logged
|
||||
last_logged_second: Option<u64>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl Default for HeatingWatchdogZone {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start_temperature: None,
|
||||
heating_start_time: None,
|
||||
fault_detected: false,
|
||||
last_logged_second: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl HeatingWatchdogZone {
|
||||
fn check(
|
||||
&mut self,
|
||||
controller: &TemperatureController,
|
||||
zone_name: &str,
|
||||
now: Instant,
|
||||
min_temperature_delta_c: f64,
|
||||
watchdog_timeout_secs: u64,
|
||||
) -> bool {
|
||||
use std::time::Duration;
|
||||
use units::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
let current_temp = controller.heating.temperature.get::<degree_celsius>();
|
||||
let target_temp = controller
|
||||
.heating
|
||||
.target_temperature
|
||||
.get::<degree_celsius>();
|
||||
let is_heating_active = controller.heating.heating;
|
||||
|
||||
// Check if heating should be active
|
||||
let should_be_heating = target_temp > current_temp;
|
||||
|
||||
// Start monitoring when heating becomes active
|
||||
if should_be_heating && is_heating_active {
|
||||
if self.start_temperature.is_none() {
|
||||
self.start_temperature = Some(current_temp);
|
||||
self.heating_start_time = Some(now);
|
||||
self.fault_detected = false;
|
||||
tracing::info!(
|
||||
"Heating watchdog started for {}: start_temp={:.1}°C, target_temp={:.1}°C",
|
||||
zone_name,
|
||||
current_temp,
|
||||
target_temp
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Reset if heating is no longer active
|
||||
if self.start_temperature.is_some() {
|
||||
tracing::info!(
|
||||
"Heating watchdog reset for {}: heating no longer active",
|
||||
zone_name
|
||||
);
|
||||
*self = HeatingWatchdogZone::default();
|
||||
}
|
||||
return false; // No fault when not monitoring
|
||||
}
|
||||
|
||||
// Check for fault if we're monitoring
|
||||
if let (Some(start_temp), Some(start_time)) =
|
||||
(self.start_temperature, self.heating_start_time)
|
||||
{
|
||||
let elapsed = now.duration_since(start_time);
|
||||
let temp_increase = current_temp - start_temp;
|
||||
|
||||
// Check if timeout exceeded without sufficient temperature increase
|
||||
if elapsed >= Duration::from_secs(watchdog_timeout_secs)
|
||||
&& temp_increase < min_temperature_delta_c
|
||||
&& !self.fault_detected
|
||||
{
|
||||
self.fault_detected = true;
|
||||
tracing::warn!(
|
||||
"Heating watchdog fault detected for {}: elapsed={:.1}s, temp_increase={:.2}°C, start_temp={:.1}°C, current_temp={:.1}°C, target_temp={:.1}°C",
|
||||
zone_name,
|
||||
elapsed.as_secs_f64(),
|
||||
temp_increase,
|
||||
start_temp,
|
||||
current_temp,
|
||||
target_temp
|
||||
);
|
||||
return true; // Fault detected
|
||||
}
|
||||
|
||||
// Log progress periodically
|
||||
let current_second = elapsed.as_secs();
|
||||
if current_second > 0 && current_second % 10 == 0 {
|
||||
let should_log = match self.last_logged_second {
|
||||
Some(last) => current_second > last,
|
||||
None => true,
|
||||
};
|
||||
|
||||
if should_log {
|
||||
tracing::debug!(
|
||||
"Heating watchdog progress for {}: elapsed={:.1}s, temp_increase={:.2}°C",
|
||||
zone_name,
|
||||
elapsed.as_secs_f64(),
|
||||
temp_increase
|
||||
);
|
||||
self.last_logged_second = Some(current_second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false // No fault
|
||||
}
|
||||
}
|
||||
|
||||
/// Watchdog state for all heating zones
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeatingWatchdog {
|
||||
pub front: HeatingWatchdogZone,
|
||||
pub middle: HeatingWatchdogZone,
|
||||
pub back: HeatingWatchdogZone,
|
||||
pub nozzle: HeatingWatchdogZone,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl Default for HeatingWatchdog {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
front: HeatingWatchdogZone::default(),
|
||||
middle: HeatingWatchdogZone::default(),
|
||||
back: HeatingWatchdogZone::default(),
|
||||
nozzle: HeatingWatchdogZone::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV3 {
|
||||
@@ -79,6 +224,15 @@ pub struct ExtruderV3 {
|
||||
/// will be initalized as false and set to true by `emit_state`
|
||||
/// This way we can signal to the client that the first state emission is a default state
|
||||
emitted_default_state: bool,
|
||||
|
||||
/// Heating safeguard configuration
|
||||
heating_safeguard_enabled: bool,
|
||||
|
||||
/// Heating watchdog state for each zone
|
||||
heating_watchdog: HeatingWatchdog,
|
||||
|
||||
/// Current heating fault state
|
||||
heating_fault_state: crate::extruder2::api::HeatingFaultState,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
@@ -143,10 +297,10 @@ impl ExtruderV3 {
|
||||
self.last_energy_calculation_time = Some(now);
|
||||
}
|
||||
|
||||
// Funktionen ohne emit_state bleiben hier
|
||||
// Functions without emit_state remain here
|
||||
|
||||
// Set all relais to ZERO
|
||||
// We dont need a function to enable again though, as the act Loop will detect the mode
|
||||
// Set all relays to ZERO
|
||||
// We don't need a function to enable again though, as the act loop will detect the mode
|
||||
fn turn_heating_off(&mut self) {
|
||||
self.temperature_controller_back.disable();
|
||||
self.temperature_controller_front.disable();
|
||||
@@ -214,4 +368,106 @@ impl ExtruderV3 {
|
||||
fn reset_inverter(&mut self) {
|
||||
self.screw_speed_controller.inverter.reset_inverter();
|
||||
}
|
||||
|
||||
/// Update heating watchdog for all zones
|
||||
/// Monitors temperature increases and detects heating faults
|
||||
fn update_heating_watchdog(&mut self, now: Instant) {
|
||||
// Watchdog parameters
|
||||
// These defaults are intentionally conservative to avoid false positives
|
||||
// on older machines or in cold environments. If we do not see at least
|
||||
// ~5°C increase within one minute, we treat this as a fault.
|
||||
const MIN_TEMPERATURE_DELTA_C: f64 = 5.0; // Minimum temperature increase in °C
|
||||
const WATCHDOG_TIMEOUT_SECS: u64 = 60; // Timeout in seconds
|
||||
|
||||
// Only monitor when in Heat or Extrude mode
|
||||
if self.mode == ExtruderV3Mode::Standby {
|
||||
// Reset all watchdog states when in standby
|
||||
self.heating_watchdog.front = HeatingWatchdogZone::default();
|
||||
self.heating_watchdog.middle = HeatingWatchdogZone::default();
|
||||
self.heating_watchdog.back = HeatingWatchdogZone::default();
|
||||
self.heating_watchdog.nozzle = HeatingWatchdogZone::default();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check all zones
|
||||
let mut fault_detected = false;
|
||||
if self.heating_watchdog.front.check(
|
||||
&self.temperature_controller_front,
|
||||
"front",
|
||||
now,
|
||||
MIN_TEMPERATURE_DELTA_C,
|
||||
WATCHDOG_TIMEOUT_SECS,
|
||||
) {
|
||||
fault_detected = true;
|
||||
}
|
||||
if self.heating_watchdog.middle.check(
|
||||
&self.temperature_controller_middle,
|
||||
"middle",
|
||||
now,
|
||||
MIN_TEMPERATURE_DELTA_C,
|
||||
WATCHDOG_TIMEOUT_SECS,
|
||||
) {
|
||||
fault_detected = true;
|
||||
}
|
||||
if self.heating_watchdog.back.check(
|
||||
&self.temperature_controller_back,
|
||||
"back",
|
||||
now,
|
||||
MIN_TEMPERATURE_DELTA_C,
|
||||
WATCHDOG_TIMEOUT_SECS,
|
||||
) {
|
||||
fault_detected = true;
|
||||
}
|
||||
if self.heating_watchdog.nozzle.check(
|
||||
&self.temperature_controller_nozzle,
|
||||
"nozzle",
|
||||
now,
|
||||
MIN_TEMPERATURE_DELTA_C,
|
||||
WATCHDOG_TIMEOUT_SECS,
|
||||
) {
|
||||
fault_detected = true;
|
||||
}
|
||||
|
||||
// If fault detected, automatically set to standby and record fault
|
||||
if fault_detected {
|
||||
// Determine which zone(s) have faults
|
||||
let fault_zones: Vec<&str> = [
|
||||
(self.heating_watchdog.front.fault_detected, "front"),
|
||||
(self.heating_watchdog.middle.fault_detected, "middle"),
|
||||
(self.heating_watchdog.back.fault_detected, "back"),
|
||||
(self.heating_watchdog.nozzle.fault_detected, "nozzle"),
|
||||
]
|
||||
.iter()
|
||||
.filter_map(|(has_fault, zone)| if *has_fault { Some(*zone) } else { None })
|
||||
.collect();
|
||||
|
||||
// In theory fault_detected guarantees at least one zone here, but use a
|
||||
// defensive default to avoid panicking on an empty list in case the logic
|
||||
// above is changed in the future.
|
||||
let first_fault_zone = fault_zones.first().copied().unwrap_or("unknown");
|
||||
|
||||
if first_fault_zone == "unknown" {
|
||||
tracing::error!(
|
||||
"Heating watchdog reported fault_detected=true but no individual zone was marked as faulty"
|
||||
);
|
||||
}
|
||||
|
||||
let fault_zone_str = if fault_zones.is_empty() {
|
||||
"unknown".to_string()
|
||||
} else {
|
||||
fault_zones.join(", ")
|
||||
};
|
||||
tracing::error!(
|
||||
"Heating fault detected in zone(s): {} - automatically setting extruder to standby",
|
||||
fault_zone_str
|
||||
);
|
||||
|
||||
// Set fault state
|
||||
self.heating_fault_state.fault_zone = Some(first_fault_zone.to_string());
|
||||
self.heating_fault_state.fault_acknowledged = false;
|
||||
|
||||
self.switch_to_standby();
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ impl MachineNewTrait for ExtruderV3 {
|
||||
Heating, mitsubishi_cs80::MitsubishiCS80,
|
||||
screw_speed_controller::ScrewSpeedController,
|
||||
},
|
||||
extruder2::{ExtruderV3Mode, api::ExtruderV3Namespace},
|
||||
extruder2::{ExtruderV3Mode, HeatingWatchdog, api::ExtruderV3Namespace},
|
||||
};
|
||||
let _ek1100 =
|
||||
get_ethercat_device::<EK1100>(hardware, params, 0, [EK1100_IDENTITY_A].to_vec());
|
||||
@@ -139,14 +139,14 @@ impl MachineNewTrait for ExtruderV3 {
|
||||
let t3 = TemperatureInput::new(el3204.clone(), EL3204Port::T3);
|
||||
let t4 = TemperatureInput::new(el3204, EL3204Port::T4);
|
||||
|
||||
// For the Relais
|
||||
// For the Relays
|
||||
let digital_out_1 = DigitalOutput::new(el2004.clone(), EL2004Port::DO1);
|
||||
let digital_out_2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
|
||||
let digital_out_3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
|
||||
let digital_out_4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
|
||||
|
||||
let pressure_sensor = AnalogInput::new(el3021, EL3021Port::AI1);
|
||||
// The Extruders temparature Controllers should disable the relais when the max_temperature is reached
|
||||
// The Extruder's temperature controllers should disable the relays when the max_temperature is reached
|
||||
let extruder_max_temperature = ThermodynamicTemperature::new::<degree_celsius>(300.0);
|
||||
// Only front heating on: These values work 0.08, 0.001, 0.007, Overshoot 0.5 undershoot ~0.7 (Problems when starting far away because of integral)
|
||||
let temperature_controller_front = TemperatureController::new(
|
||||
@@ -241,6 +241,12 @@ impl MachineNewTrait for ExtruderV3 {
|
||||
screw_speed_controller,
|
||||
emitted_default_state: false,
|
||||
last_status_hash: None,
|
||||
heating_safeguard_enabled: true,
|
||||
heating_watchdog: HeatingWatchdog::default(),
|
||||
heating_fault_state: crate::extruder2::api::HeatingFaultState {
|
||||
fault_zone: None,
|
||||
fault_acknowledged: false,
|
||||
},
|
||||
};
|
||||
extruder.emit_state();
|
||||
Ok(extruder)
|
||||
|
||||
@@ -10,7 +10,7 @@ use units::thermodynamic_temperature::degree_celsius;
|
||||
pub struct TemperatureController {
|
||||
pub pid: PidController,
|
||||
temperature_sensor: TemperatureInput,
|
||||
relais: DigitalOutput,
|
||||
relay: DigitalOutput,
|
||||
pub heating: Heating,
|
||||
pub target_temp: ThermodynamicTemperature,
|
||||
window_start: Instant,
|
||||
@@ -24,7 +24,7 @@ pub struct TemperatureController {
|
||||
|
||||
impl TemperatureController {
|
||||
pub fn disable(&mut self) {
|
||||
self.relais.set(false);
|
||||
self.relay.set(false);
|
||||
self.heating.heating = false;
|
||||
self.disallow_heating();
|
||||
}
|
||||
@@ -36,7 +36,7 @@ impl TemperatureController {
|
||||
target_temp: ThermodynamicTemperature,
|
||||
max_temperature: ThermodynamicTemperature,
|
||||
temperature_sensor: TemperatureInput,
|
||||
relais: DigitalOutput,
|
||||
relay: DigitalOutput,
|
||||
heating: Heating,
|
||||
pwm_duration: Duration,
|
||||
heating_element_wattage: f64,
|
||||
@@ -47,7 +47,7 @@ impl TemperatureController {
|
||||
target_temp,
|
||||
window_start: Instant::now(),
|
||||
temperature_sensor,
|
||||
relais,
|
||||
relay,
|
||||
heating,
|
||||
heating_allowed: false,
|
||||
pwm_period: pwm_duration,
|
||||
@@ -86,8 +86,8 @@ impl TemperatureController {
|
||||
self.heating.temperature = temperature_celsius;
|
||||
|
||||
if self.heating.temperature > self.max_temperature {
|
||||
// disable the relais and return
|
||||
self.relais.set(false);
|
||||
// Disable the relays and return
|
||||
self.relay.set(false);
|
||||
self.heating.heating = false;
|
||||
return;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ impl TemperatureController {
|
||||
|
||||
// Relay is ON if within duty cycle window
|
||||
let on = elapsed < on_time;
|
||||
self.relais.set(on);
|
||||
self.relay.set(on);
|
||||
self.heating.heating = on;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user