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}