code wiki / (root) / nx_vessel_io.nx

nx_vessel_io.nx source

↩ module page · 58 lines · 2361 B

1// nx_vessel_io.nx -- H1 of the HARDWARE-IN-THE-LOOP ladder: realistic 2// SENSOR and ACTUATOR models, so the real control software is tested 3// against imperfect hardware -- not the perfect readings a unit test gives. 4// 5// A real temperature probe has: thermal LAG (its own mass takes time to 6// reach the liquid's temperature), QUANTIZATION (a finite-bit ADC), and 7// NOISE. A real heater is PWM bang-bang with a power cap. H1 models all 8// of these so the R1 controller can be flown against them in the loop -- 9// catching instability or offset from sensor lag BEFORE the build. 10// 11// Deterministic by construction (no RNG): "noise" is a reproducible dither 12// keyed on the step index, so the gate is exact and repeatable. 13// 14// genealogy_id: hardware_in_the_loop_sim + first_order_sensor_lag + pwm 15 16import "nx_syscalls.nx" 17 18struct NxSensor { 19 state_mc: i64, // internal lagged reading (milli-C) 20 lag_alpha_milli: i64, // 1000 = instant; lower = laggier (per step) 21 quantum_mc: i64, // ADC resolution (e.g. 100 = 0.1 C); 0 = none 22 noise_amp_mc: i64, // +/- dither amplitude; 0 = none 23} 24 25func nx_sensor_new(init_mc: i64, lag_alpha_milli: i64, 26 quantum_mc: i64, noise_amp_mc: i64) -> *NxSensor { 27 let s: *NxSensor = (sys_mmap(32)) as *NxSensor 28 s.state_mc = init_mc 29 s.lag_alpha_milli = lag_alpha_milli 30 s.quantum_mc = quantum_mc 31 s.noise_amp_mc = noise_amp_mc 32 return s 33} 34 35// Advance the sensor one step against the true temperature; return the 36// measured reading (lagged, dithered, quantized). 37func nx_sensor_update(s: *NxSensor, true_temp_mc: i64, step: i64) -> i64 { 38 let delta: i64 = (true_temp_mc - s.state_mc) * s.lag_alpha_milli / 1000 39 s.state_mc = s.state_mc + delta 40 var noise: i64 = 0 41 if s.noise_amp_mc > 0 { 42 let span: i64 = 2 * s.noise_amp_mc + 1 43 noise = (step * 7919) % span - s.noise_amp_mc 44 } 45 var m: i64 = s.state_mc + noise 46 if s.quantum_mc > 0 { 47 m = (m / s.quantum_mc) * s.quantum_mc 48 } 49 return m 50} 51 52// PWM actuator: a duty command (milli, 0..1000) realized as on/off for the 53// given phase (0..999). Time-averaged over a cycle this equals the duty; 54// the vessel's thermal mass filters the ripple. 55func nx_heater_pwm(duty_milli: i64, phase: i64) -> i64 { 56 if phase < duty_milli { return 1000 } 57 return 0 58}