diff --git a/control-core/src/machines/manager_iter.rs b/control-core/src/machines/manager_iter.rs index 798d379cf..6d0ec4092 100644 --- a/control-core/src/machines/manager_iter.rs +++ b/control-core/src/machines/manager_iter.rs @@ -71,7 +71,7 @@ impl<'a> Iterator for MachineManagerIterator<'a> { impl MachineManager { // Returns an iterator over all machines (both ethercat and serial) - pub fn iter(&self) -> MachineManagerIterator { + pub fn iter(&'_ self) -> MachineManagerIterator<'_> { MachineManagerIterator { iter: MachineManagerIter::EthercatMachines { iter: self.ethercat_machines.iter(), diff --git a/electron/src/machines/extruder/extruder2/Extruder2ControlPage.tsx b/electron/src/machines/extruder/extruder2/Extruder2ControlPage.tsx index eb281dccd..8857ced98 100644 --- a/electron/src/machines/extruder/extruder2/Extruder2ControlPage.tsx +++ b/electron/src/machines/extruder/extruder2/Extruder2ControlPage.tsx @@ -29,7 +29,6 @@ export function Extruder2ControlPage() { pressure, motorScrewRpm, - motorPower, combinedPower, setExtruderMode, @@ -119,9 +118,11 @@ export function Extruder2ControlPage() { Inverter is overloaded! Please check the extruder and reduce load if necessary. - ) : state?.inverter_status_state.fault_occurence == true ? ( + ) : state?.inverter_status_state.fault_occurence == true && + state?.inverter_status_state.fault ? ( - Inverter encountered an error!! Press the restart button in Config + Inverter stopped: + {state?.inverter_status_state.fault?.fault_description} ) : state?.inverter_status_state.running == true && state.inverter_status_state.fault_occurence == false ? ( diff --git a/electron/src/machines/extruder/extruder2/extruder2Namespace.ts b/electron/src/machines/extruder/extruder2/extruder2Namespace.ts index 0b4e81b20..4315e3d81 100644 --- a/electron/src/machines/extruder/extruder2/extruder2Namespace.ts +++ b/electron/src/machines/extruder/extruder2/extruder2Namespace.ts @@ -100,6 +100,15 @@ export const heatingStatesSchema = z.object({ back: heatingStateSchema, middle: heatingStateSchema, }); +// pub fault_code: FaultCode, +// pub fault_description: String, +// pub ts: Instant, + +export const faultStateSchema = z.object({ + fault_code: z.number(), + fault_description: z.string(), + time_stamp: z.number(), +}); /** * Extruder settings state schema @@ -113,6 +122,7 @@ export const extruderSettingsStateSchema = z.object({ * Inverter status state schema */ export const inverterStatusStateSchema = z.object({ + fault: faultStateSchema.nullable(), running: z.boolean(), forward_running: z.boolean(), reverse_running: z.boolean(), diff --git a/server/src/machines/extruder1/api.rs b/server/src/machines/extruder1/api.rs index 102a0b159..352332d52 100644 --- a/server/src/machines/extruder1/api.rs +++ b/server/src/machines/extruder1/api.rs @@ -75,7 +75,7 @@ impl LiveValuesEvent { } } -#[derive(Serialize, Debug, Clone, PartialEq, BuildEvent)] +#[derive(Serialize, Debug, Clone, BuildEvent)] pub struct StateEvent { pub is_default_state: bool, /// rotation state @@ -144,8 +144,16 @@ pub struct ExtruderSettingsState { pub pressure_limit_enabled: bool, } -#[derive(Serialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Debug, Clone)] +pub struct FaultState { + pub fault_code: u16, + pub fault_description: String, + pub time_stamp: u64, +} + +#[derive(Serialize, Debug, Clone)] pub struct InverterStatusState { + pub fault: Option, /// RUN (Inverter running) pub running: bool, /// Forward running motor spins forward diff --git a/server/src/machines/extruder1/mitsubishi_cs80/mod.rs b/server/src/machines/extruder1/mitsubishi_cs80/mod.rs index eb22ee53b..0b353d19e 100644 --- a/server/src/machines/extruder1/mitsubishi_cs80/mod.rs +++ b/server/src/machines/extruder1/mitsubishi_cs80/mod.rs @@ -4,7 +4,7 @@ use control_core::modbus::{ modbus_serial_interface::ModbusSerialInterface, }; use ethercat_hal::io::serial_interface::SerialInterface; -use std::time::{Duration, Instant}; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use uom::si::{ electric_current::centiampere, electric_potential::centivolt, @@ -36,6 +36,8 @@ enum MitsubishiCS80Register { //RunningFrequencyEEPROM, /// Register 40201 MotorStatus, + /// Register 40501 + FaultHistory, } impl MitsubishiCS80Register { @@ -45,6 +47,7 @@ impl MitsubishiCS80Register { Self::InverterStatusAndControl => 0x8, Self::RunningFrequencyRAM => 0x0d, Self::MotorStatus => 0x00C8, // a0x00C8 = frequency , 0x00C9 = current ,0x00C10 = voltage + Self::FaultHistory => 0x01F4, } } @@ -54,35 +57,39 @@ impl MitsubishiCS80Register { } /// These Requests Serve as Templates for controlling the inverter +/// i added None = 0 ... and so on so its clearer how the impl From works #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] pub enum MitsubishiCS80Requests { - None, + None = 0, /// Register 40002, Reset/Restart the Inverter - ResetInverter, + ResetInverter = 1, /// Register 40004, Clear ALL parameters - ClearAllParameters, + ClearAllParameters = 2, /// Register 40006, Clear a non communication parameter - ClearNonCommunicationParameter, + ClearNonCommunicationParameter = 3, /// Register 40007, Clear all Non Communication related Parameters - ClearNonCommunicationParameters, + ClearNonCommunicationParameters = 4, /// Register 40009, Read Inverter Status - ReadInverterStatus, + ReadInverterStatus = 5, /// Register 40009, Stops the Motor - StopMotor, + StopMotor = 6, /// Register 40009, Starts the Motor in Forward Rotation - StartForwardRotation, + StartForwardRotation = 7, /// Register 40009, Starts the Motor in Reverse Rotation - StartReverseRotation, + StartReverseRotation = 8, /// Register 40014, Read the current frequency the motor runs at (RAM) - ReadRunningFrequency, + ReadRunningFrequency = 9, /// Register 40014, Write the frequency - WriteRunningFrequency, + WriteRunningFrequency = 10, /// Read Register 40201, 40202 and 40203 frequency,current and voltage - ReadMotorStatus, + ReadMotorStatus = 11, /// Write "Arbitrary" Parameters - WriteParameter, + WriteParameter = 12, + /// Read Last 3 Inverter Faults (including the current one) + ReadFaults = 13, } +/// None = 0, ResetInverter = 1 ... like its defined in the enum impl From for u32 { fn from(request: MitsubishiCS80Requests) -> Self { request as u32 @@ -107,6 +114,7 @@ impl TryFrom for MitsubishiCS80Requests { 10 => Ok(Self::WriteRunningFrequency), 11 => Ok(Self::ReadMotorStatus), 12 => Ok(Self::WriteParameter), + 13 => Ok(Self::ReadFaults), _ => Err(()), } } @@ -244,6 +252,19 @@ impl From for MitsubishiCS80Request { RequestType::ReadWrite, u16::MAX, ), + MitsubishiCS80Requests::ReadFaults => { + let reg_bytes = MitsubishiCS80Register::FaultHistory.address_be_bytes(); + Self::new( + ModbusRequest { + slave_id: 1, + function_code: ModbusFunctionCode::ReadHoldingRegister, + data: vec![reg_bytes[0], reg_bytes[1], 0x0, 0x3], // read 3 registers + }, + request, + RequestType::OperationCommand, + u16::MAX, // MAX because we want it to be sent asap when it is added to the "queue" + ) + } // For unimplemented variants, return a default request _ => Self::new( @@ -281,6 +302,165 @@ pub struct MotorStatus { pub voltage: ElectricPotential, } +#[derive(Debug, Clone)] +pub enum FaultCode { + ExcessivePressure = 0x9, // This is a custom FaultCode for our Pressure limit + OvercurrentTripAcceleration = 0x10, + OvercurrentTripConstSpeed = 0x11, + OvercurrentTripDeceleration = 0x12, + RegenerativeOvervoltageTripAcceleration = 0x20, + RegenerativeOvervoltageTripConstSpeed = 0x21, + RegenerativeOvervoltageTripDeceleration = 0x22, + InverterOverloadTrip = 0x30, // electronic thermal O/L + MotorOverloadTrip = 0x31, // electronic thermal O/L, not really sure what the difference is ... + HeatsinkOverheat = 0x40, + Undervoltage = 0x51, + InputPhaseLoss = 0x52, + StallPreventionStop = 0x60, + OutputSideEarthFaulOverCurrent = 0x80, + OutputPhaseLoss = 0x81, + ExternalThermalRelayOperation = 0x90, + ParamStorageFault1 = 0xb0, + ParamStorageFault2 = 0xb3, + ParamUnitDisconnect = 0xb1, + RetryCountExcess = 0xb2, + CPUFAULT1 = 0xc0, + CPUFAULT2 = 0xf5, + AbnormalOutputCurrentDetection = 0xc4, + InrushCurrentLimitCircuitFault = 0xc5, + FourMilliAmpereInputFault = 0xe4, + InverterOutputFault = 0xfa, +} + +impl From for u16 { + fn from(fault: FaultCode) -> Self { + fault as u16 + } +} + +impl TryFrom for FaultCode { + type Error = anyhow::Error; + + fn try_from(value: u16) -> Result { + match value { + 0x9 => Ok(FaultCode::ExcessivePressure), + 0x10 => Ok(FaultCode::OvercurrentTripAcceleration), + 0x11 => Ok(FaultCode::OvercurrentTripConstSpeed), + 0x12 => Ok(FaultCode::OvercurrentTripDeceleration), + 0x20 => Ok(FaultCode::RegenerativeOvervoltageTripAcceleration), + 0x21 => Ok(FaultCode::RegenerativeOvervoltageTripConstSpeed), + 0x22 => Ok(FaultCode::RegenerativeOvervoltageTripDeceleration), + 0x30 => Ok(FaultCode::InverterOverloadTrip), + 0x31 => Ok(FaultCode::MotorOverloadTrip), + 0x40 => Ok(FaultCode::HeatsinkOverheat), + 0x51 => Ok(FaultCode::Undervoltage), + 0x52 => Ok(FaultCode::InputPhaseLoss), + 0x60 => Ok(FaultCode::StallPreventionStop), + 0x80 => Ok(FaultCode::OutputSideEarthFaulOverCurrent), + 0x81 => Ok(FaultCode::OutputPhaseLoss), + 0x90 => Ok(FaultCode::ExternalThermalRelayOperation), + 0xb0 => Ok(FaultCode::ParamStorageFault1), + 0xb1 => Ok(FaultCode::ParamUnitDisconnect), + 0xb2 => Ok(FaultCode::RetryCountExcess), + 0xb3 => Ok(FaultCode::ParamStorageFault2), + 0xc0 => Ok(FaultCode::CPUFAULT1), + 0xc4 => Ok(FaultCode::AbnormalOutputCurrentDetection), + 0xc5 => Ok(FaultCode::InrushCurrentLimitCircuitFault), + 0xe4 => Ok(FaultCode::FourMilliAmpereInputFault), + 0xf5 => Ok(FaultCode::CPUFAULT2), + 0xfa => Ok(FaultCode::InverterOutputFault), + _ => Err(anyhow::anyhow!( + "FAULTCODE IS UNKNOWN! Could indicate a defect" + )), + } + } +} + +#[derive(Debug, Clone)] +pub struct Fault { + pub fault_code: FaultCode, + pub fault_description: String, + pub ts: u64, +} + +impl FaultCode { + fn description(&self) -> String { + match self { + FaultCode::ExcessivePressure => { + "Pressure exceeds limit, try more heat or less rpm".to_owned() + } + FaultCode::OvercurrentTripAcceleration => { + "Overcurrent trip during acceleration".to_owned() + } + FaultCode::OvercurrentTripConstSpeed => { + "Overcurrent trip during constant speed".to_owned() + } + FaultCode::OvercurrentTripDeceleration => { + "Overcurrent trip during deceleration or stop".to_owned() + } + FaultCode::RegenerativeOvervoltageTripAcceleration => { + "Regenerative overvoltage trip during acceleration".to_owned() + } + FaultCode::RegenerativeOvervoltageTripConstSpeed => { + "Regenerative overvoltage trip during constant speed".to_owned() + } + FaultCode::RegenerativeOvervoltageTripDeceleration => { + "Regenerative overvoltage trip during deceleration or stop".to_owned() + } + FaultCode::InverterOverloadTrip => { + "Inverter overload trip (electronic thermal O/L)".to_owned() + } + FaultCode::MotorOverloadTrip => { + "Motor overload trip (electronic thermal O/L)".to_owned() + } + FaultCode::HeatsinkOverheat => "Heatsink overheat".to_owned(), + FaultCode::Undervoltage => "Undervoltage".to_owned(), + FaultCode::InputPhaseLoss => "Input phase loss".to_owned(), + FaultCode::StallPreventionStop => "Stall prevention stop".to_owned(), + FaultCode::OutputSideEarthFaulOverCurrent => { + "Output side earth (ground) fault overcurrent".to_owned() + } + FaultCode::OutputPhaseLoss => "Output phase loss".to_owned(), + FaultCode::ExternalThermalRelayOperation => { + "External thermal relay operation".to_owned() + } + FaultCode::ParamStorageFault1 => "Parameter storage device fault".to_owned(), + FaultCode::ParamStorageFault2 => "Parameter storage device fault".to_owned(), + FaultCode::ParamUnitDisconnect => "PU disconnection".to_owned(), + FaultCode::RetryCountExcess => "Retry count excess".to_owned(), + FaultCode::CPUFAULT1 => "CPU fault".to_owned(), + FaultCode::CPUFAULT2 => "CPU fault".to_owned(), + FaultCode::AbnormalOutputCurrentDetection => { + "Abnormal output current detection".to_owned() + } + FaultCode::InrushCurrentLimitCircuitFault => { + "Inrush current limit circuit fault".to_owned() + } + FaultCode::FourMilliAmpereInputFault => "4 mA input fault".to_owned(), + FaultCode::InverterOutputFault => "Inverter output fault".to_owned(), + } + } +} + +pub fn build_fault(fault_code: u16) -> Option { + let fault_code: Result = fault_code.try_into(); + let fault_code = match fault_code { + Ok(fault_code) => fault_code, + Err(_) => return None, + }; + let fault_description = fault_code.description(); + let fault = Fault { + fault_code, + fault_description, + ts: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + }; + + return Some(fault); +} + #[derive(Debug)] pub struct MitsubishiCS80 { // Communication @@ -288,6 +468,7 @@ pub struct MitsubishiCS80 { pub motor_status: MotorStatus, pub modbus_serial_interface: ModbusSerialInterface, pub last_ts: Instant, + pub last_fault: Option, } #[derive(Debug, Clone, Copy)] @@ -347,6 +528,7 @@ impl MitsubishiCS80 { last_ts: Instant::now(), motor_status: MotorStatus::default(), status: MitsubishiCS80Status::default(), + last_fault: None, } } @@ -379,21 +561,51 @@ impl MitsubishiCS80 { }; let bits: &BitSlice = BitSlice::<_, Lsb0>::from_slice(&status_bytes); - if bits.len() >= 16 { - self.status = MitsubishiCS80Status { - fault_occurence: bits[7], - running: bits[8], - forward_running: bits[9], - reverse_running: bits[10], - su: bits[11], - ol: bits[12], - no_function: bits[13], - fu: bits[14], - abc_: bits[15], - }; + if bits.len() < 16 { + return; + } + + self.status = MitsubishiCS80Status { + fault_occurence: bits[7], + running: bits[8], + forward_running: bits[9], + reverse_running: bits[10], + su: bits[11], + ol: bits[12], + no_function: bits[13], + fu: bits[14], + abc_: bits[15], + }; + + if self.status.fault_occurence { + // add request to check the specific fault + self.add_request(MitsubishiCS80Requests::ReadFaults.into()); } } + fn handle_read_fault(&mut self, resp: &ModbusResponse) { + if resp.data[0] % 2 == 1 { + tracing::error!( + "[{}::handle_read_fault] invalid fault data received", + module_path!() + ); + + // if data length is odd, dont continue + return; + } + + let fault_code = u16::from_be_bytes([resp.data[1], resp.data[2]]); + let fault = build_fault(fault_code); + + if !fault.is_none() { + tracing::info!( + "inverter fault occured: {}", + &fault.clone().unwrap().fault_description, + ); + } + self.last_fault = fault; + } + fn handle_response(&mut self, control_request_type: u32) { let response_type = match MitsubishiCS80Requests::try_from(control_request_type) { Ok(request_type) => request_type, @@ -406,11 +618,11 @@ impl MitsubishiCS80 { match response_type { MitsubishiCS80Requests::ReadInverterStatus => { - self.handle_read_inverter_status(&response); - } - MitsubishiCS80Requests::ReadMotorStatus => { - self.handle_motor_status(&response); + self.handle_read_inverter_status(&response) } + MitsubishiCS80Requests::ReadMotorStatus => self.handle_motor_status(&response), + MitsubishiCS80Requests::ReadFaults => self.handle_read_fault(&response), + // Other request types don't need response handling _ => {} } @@ -474,8 +686,10 @@ impl MitsubishiCS80 { return; } - self.add_request(MitsubishiCS80Requests::ReadInverterStatus.into()); - self.add_request(MitsubishiCS80Requests::ReadMotorStatus.into()); + // self.add_request(MitsubishiCS80Requests::ReadInverterStatus.into()); + // self.add_request(MitsubishiCS80Requests::ReadMotorStatus.into()); + self.add_request(MitsubishiCS80Requests::ReadFaults.into()); + self.modbus_serial_interface.act(now).await; self.handle_response(self.modbus_serial_interface.last_message_id); } diff --git a/server/src/machines/extruder1/mod.rs b/server/src/machines/extruder1/mod.rs index b271d456e..8567913ec 100644 --- a/server/src/machines/extruder1/mod.rs +++ b/server/src/machines/extruder1/mod.rs @@ -1,3 +1,4 @@ +use crate::machines::extruder1::api::FaultState; use crate::machines::{MACHINE_EXTRUDER_V1, VENDOR_QITECH}; use api::{ ExtruderSettingsState, ExtruderV2Events, ExtruderV2Namespace, HeatingState, HeatingStates, @@ -20,6 +21,7 @@ use uom::si::{ pressure::bar, thermodynamic_temperature::degree_celsius, }; + pub mod act; pub mod api; pub mod mitsubishi_cs80; @@ -79,6 +81,15 @@ pub struct ExtruderV2 { impl ExtruderV2 { pub fn build_state_event(&mut self) -> StateEvent { + let fault = match self.screw_speed_controller.get_last_fault() { + Some(fault) => Some(FaultState { + fault_code: fault.fault_code.into(), + fault_description: fault.fault_description, + time_stamp: fault.ts, + }), + None => None, + }; + StateEvent { is_default_state: !std::mem::replace(&mut self.emitted_default_state, true), rotation_state: RotationState { @@ -156,6 +167,7 @@ impl ExtruderV2 { output_frequency_detection: self.screw_speed_controller.inverter.status.fu, abc_fault: self.screw_speed_controller.inverter.status.abc_, fault_occurence: self.screw_speed_controller.inverter.status.fault_occurence, + fault: fault, }, pid_settings: PidSettingsStates { temperature: PidSettings { @@ -252,10 +264,10 @@ impl ExtruderV2 { } pub fn emit_state(&mut self) { - let state = self.build_state_event(); - self.last_state_event = Some(state.clone()); - let event = state.build(); - self.namespace.emit(ExtruderV2Events::State(event)); + let event = self.build_state_event(); + self.namespace + .emit(ExtruderV2Events::State(event.clone().build())); + self.last_state_event = Some(event); } // Extruder Settings Api Impl diff --git a/server/src/machines/extruder1/screw_speed_controller.rs b/server/src/machines/extruder1/screw_speed_controller.rs index 9728873d1..a9f436f96 100644 --- a/server/src/machines/extruder1/screw_speed_controller.rs +++ b/server/src/machines/extruder1/screw_speed_controller.rs @@ -15,6 +15,8 @@ use uom::si::{ pressure::bar, }; +use crate::machines::extruder1::mitsubishi_cs80::Fault; + use super::mitsubishi_cs80::{MitsubishiCS80, MotorStatus}; #[derive(Debug)] @@ -23,15 +25,21 @@ pub struct ScrewSpeedController { pub target_pressure: Pressure, pub target_rpm: AngularVelocity, pub inverter: MitsubishiCS80, + pressure_sensor: AnalogInput, last_update: Instant, + uses_rpm: bool, forward_rotation: bool, + transmission: FixedTransmission, frequency: Frequency, + maximum_frequency: Frequency, minimum_frequency: Frequency, + motor_on: bool, + nozzle_pressure_limit: Pressure, nozzle_pressure_limit_enabled: bool, } @@ -64,6 +72,10 @@ impl ScrewSpeedController { } } + pub fn get_last_fault(&mut self) -> Option { + return self.inverter.last_fault.clone(); + } + pub fn get_motor_enabled(&mut self) -> bool { return self.motor_on; } @@ -200,18 +212,18 @@ impl ScrewSpeedController { return Pressure::new::(actual_pressure); } + /// check if current pressure exceeds our limit if enabled + pub fn check_pressure_exceed_limit(&mut self, pressure: Pressure) -> bool { + return (pressure >= self.nozzle_pressure_limit) + && self.nozzle_pressure_limit_enabled + && self.motor_on; + } + pub fn update(&mut self, now: Instant, is_extruding: bool) { - // TODO: move this logic elsewhere or make non async block_on(self.inverter.act(now)); - let wiring_error = self.get_wiring_error(); - if wiring_error { - // emit in act - } - let measured_pressure = self.get_pressure(); - - if !self.uses_rpm && !is_extruding && self.motor_on { + if !self.uses_rpm && !is_extruding { let frequency = Frequency::new::(0.0); self.inverter.set_frequency_target(frequency); self.turn_motor_off(); @@ -219,20 +231,17 @@ impl ScrewSpeedController { return; } - if (measured_pressure >= self.nozzle_pressure_limit) - && self.nozzle_pressure_limit_enabled - && self.motor_on - { + if self.check_pressure_exceed_limit(measured_pressure) { self.turn_motor_off(); self.last_update = now; return; } - if is_extruding == true && self.motor_on == false { + if is_extruding && !self.motor_on { self.turn_motor_on(); } - if !self.uses_rpm && is_extruding == true { + if !self.uses_rpm && is_extruding { let error = self.target_pressure - measured_pressure; let freq_change = self.pid.update(error.get::(), now);