code wiki / (root) / nx_vessel_faults.nx

nx_vessel_faults.nx source

↩ module page · 74 lines · 3279 B

1// nx_vessel_faults.nx -- H2 of the HARDWARE-IN-THE-LOOP ladder: FAULT 2// INJECTION (the "iron bird" / FMEA rig). Aerospace proves a system fails 3// safe by deliberately breaking it in simulation -- stuck actuators, dead 4// sensors, lost power -- before it ever flies. H2 does the same for the 5// fermenter: inject each hardware failure and prove the design contains 6// the hazard or flags the batch, never silently overheating or shipping 7// unsafe food. 8// 9// THE LAST LINE OF DEFENSE is an INDEPENDENT THERMAL CUTOFF -- a bimetallic 10// thermostat / thermal fuse that acts on the TRUE temperature, wired around 11// the controller. It catches the two failures the controller cannot: a 12// heater stuck on (the command is irrelevant) and a dead sensor (the 13// controller is blind). This is exactly how real appliances are made safe. 14// 15// genealogy_id: fmea_fault_injection + independent_thermal_cutoff 16// + nishi_ferment_safety_r0 17 18import "nx_syscalls.nx" 19import "nx_ferment_safety.nx" 20import "nx_ferment_thermal.nx" 21import "nx_vessel_thermal.nx" 22import "nx_vessel_io.nx" 23 24const NX_VFAULT_NONE: i64 = 0 25const NX_VFAULT_HEATER_STUCK_ON: i64 = 1 26const NX_VFAULT_SENSOR_STUCK_LOW:i64 = 2 27const NX_VFAULT_POWER_LOSS: i64 = 3 28 29// Independent hardware over-temp cutoff (thermal fuse) on TRUE temperature. 30// cutoff_mc = 0 disables it (to demonstrate it is necessary). 31func nx_thermal_cutoff(true_temp_mc: i64, cutoff_mc: i64, requested_duty: i64) -> i64 { 32 if cutoff_mc <= 0 { return requested_duty } 33 if true_temp_mc >= cutoff_mc { return 0 } 34 return requested_duty 35} 36 37// Inject a fault into the realized heater duty. 38func nx_fault_heater_duty(fault: i64, controller_duty: i64) -> i64 { 39 if fault == NX_VFAULT_HEATER_STUCK_ON { return 1000 } 40 if fault == NX_VFAULT_POWER_LOSS { return 0 } 41 return controller_duty 42} 43 44// Inject a fault into the sensor reading the controller sees. 45func nx_fault_sensor_read(fault: i64, measured: i64, stuck_value: i64) -> i64 { 46 if fault == NX_VFAULT_SENSOR_STUCK_LOW { return stuck_value } 47 return measured 48} 49 50// Run a complete fault scenario through the full stack (controller + sensor 51// + thermal twin + cutoff). Returns the MAXIMUM true temperature reached 52// (the physical-hazard metric); writes the FINAL true temperature to 53// out_final[0] (the did-it-ferment metric). 54func nx_vessel_fault_run(env: *NxFermentSafetyEnvelope, twin: *NxVesselThermal, 55 fault: i64, cutoff_mc: i64, steps: i64, 56 out_final: *i64) -> i64 { 57 let ctrl: *NxFermentThermal = nx_ferment_thermal_new(env, 43000, 700, 4, 0, 4000000, 1000) 58 let sens: *NxSensor = nx_sensor_new(20000, 300, 100, 100) 59 var truet: i64 = 20000 60 var maxt: i64 = truet 61 var step: i64 = 0 62 while step < steps { 63 var meas: i64 = nx_sensor_update(sens, truet, step) 64 meas = nx_fault_sensor_read(fault, meas, 20000) 65 let cmd: i64 = nx_ferment_thermal_step(ctrl, meas) 66 var duty: i64 = nx_fault_heater_duty(fault, cmd) 67 duty = nx_thermal_cutoff(truet, cutoff_mc, duty) 68 truet = nx_vessel_thermal_step(twin, truet, duty, 60) 69 if truet > maxt { maxt = truet } 70 step = step + 1 71 } 72 out_final[0] = truet 73 return maxt 74}