nx_ac_fdd_test.nx source
↩ module page · 157 lines · 5315 B
1// nx_ac_fdd_test.nx -- gate for the refrigerant/air-side FDD verdict.
2//
3// Proves each published fault signature (NIST AFDD / ASHRAE APAR) fires under
4// Title-24-default thresholds, precedence holds (frozen coil first), and the
5// verdict TRACKS the inputs (liar-kill: same high superheat, low vs high
6// subcooling -> undercharge vs liquid-restriction, not a constant).
7//
8// expect_exit: 0
9// license_tier: ORIGINAL
10
11import "nx_syscalls_x86_64.nx"
12import "nx_ac_fdd.nx"
13
14// nominal reading -> NOMINAL verdict; each test overrides one channel
15func fdd_reset_in(p: *FddInput) -> i64 {
16 p.superheat_mC = 8333
17 p.subcool_mC = 5556
18 p.suction_sat_mC = 5000
19 p.evap_split_mC = 11000
20 p.cond_over_ambient_mC = 15000
21 p.cfm_per_ton = 400
22 p.cph_x10 = 20
23 return 0
24}
25
26// Title 24 / NIST default thresholds
27func fdd_reset_th(t: *FddThresh) -> i64 {
28 t.target_superheat_mC = 8333
29 t.superheat_band_mC = 2778
30 t.target_subcool_mC = 5556
31 t.subcool_band_mC = 2778
32 t.split_ceiling_mC = 13889
33 t.cond_over_ambient_ceiling_mC = 25000
34 t.cfm_per_ton_floor = 350
35 t.max_cph_x10 = 35
36 t.freeze_suction_sat_mC = 0
37 return 0
38}
39
40func main() -> i64 {
41 if nx_fdd_verdict_is_valid(NX_FDD_NOMINAL) != 1 { return 1 }
42 if nx_fdd_verdict_is_valid(NX_FDD_N) != 0 { return 2 }
43 if nx_fdd_verdict_is_valid(-1) != 0 { return 3 }
44
45 let rd: *FddInput = sys_mmap(128) as *FddInput
46 let th: *FddThresh = sys_mmap(128) as *FddThresh
47 let o: *FddOut = sys_mmap(64) as *FddOut
48 fdd_reset_th(th)
49
50 // ===== NOMINAL baseline =========================================
51 fdd_reset_in(rd)
52 nx_ac_fdd_analyze(rd, th, o)
53 if o.verdict != NX_FDD_NOMINAL { return 10 }
54
55 // ===== UNDERCHARGE: high superheat + low subcooling =============
56 fdd_reset_in(rd)
57 rd.superheat_mC = 17000
58 rd.subcool_mC = 1500
59 nx_ac_fdd_analyze(rd, th, o)
60 if o.verdict != NX_FDD_UNDERCHARGE { return 20 }
61 if o.sh_high != 1 { return 21 }
62 if o.sc_low != 1 { return 22 }
63
64 // ===== UNDERCHARGE with subcool sensor ABSENT ===================
65 fdd_reset_in(rd)
66 rd.superheat_mC = 17000
67 rd.subcool_mC = NX_FDD_ABSENT
68 nx_ac_fdd_analyze(rd, th, o)
69 if o.verdict != NX_FDD_UNDERCHARGE { return 25 }
70
71 // ===== LIQUID_RESTRICTION: high superheat AND high subcooling ===
72 fdd_reset_in(rd)
73 rd.superheat_mC = 17000
74 rd.subcool_mC = 11000
75 nx_ac_fdd_analyze(rd, th, o)
76 if o.verdict != NX_FDD_LIQUID_RESTRICTION { return 30 }
77 if o.sh_high != 1 { return 31 }
78 if o.sc_high != 1 { return 32 }
79
80 // ===== OVERCHARGE: high subcooling, superheat not high ==========
81 fdd_reset_in(rd)
82 rd.superheat_mC = 6000
83 rd.subcool_mC = 12000
84 nx_ac_fdd_analyze(rd, th, o)
85 if o.verdict != NX_FDD_OVERCHARGE { return 40 }
86
87 // ===== FOULED_CONDENSER: condensing far above ambient ===========
88 fdd_reset_in(rd)
89 rd.superheat_mC = NX_FDD_ABSENT
90 rd.subcool_mC = NX_FDD_ABSENT
91 rd.cond_over_ambient_mC = 30000
92 nx_ac_fdd_analyze(rd, th, o)
93 if o.verdict != NX_FDD_FOULED_CONDENSER { return 50 }
94
95 // ===== LOW_AIRFLOW: high evaporator split =======================
96 fdd_reset_in(rd)
97 rd.evap_split_mC = 16000
98 nx_ac_fdd_analyze(rd, th, o)
99 if o.verdict != NX_FDD_LOW_AIRFLOW { return 60 }
100
101 // ===== LOW_AIRFLOW: CFM/ton below the 350 floor =================
102 fdd_reset_in(rd)
103 rd.evap_split_mC = 11000
104 rd.cfm_per_ton = 300
105 nx_ac_fdd_analyze(rd, th, o)
106 if o.verdict != NX_FDD_LOW_AIRFLOW { return 65 }
107
108 // ===== FROZEN_COIL: suction saturation below 0 C ================
109 fdd_reset_in(rd)
110 rd.suction_sat_mC = -2000
111 nx_ac_fdd_analyze(rd, th, o)
112 if o.verdict != NX_FDD_FROZEN_COIL { return 70 }
113
114 // ===== SHORT_CYCLING: cycles/hr over ceiling ====================
115 fdd_reset_in(rd)
116 rd.cph_x10 = 120
117 nx_ac_fdd_analyze(rd, th, o)
118 if o.verdict != NX_FDD_SHORT_CYCLING { return 80 }
119
120 // ===== INSUFFICIENT_DATA: no channels ===========================
121 rd.superheat_mC = NX_FDD_ABSENT
122 rd.subcool_mC = NX_FDD_ABSENT
123 rd.suction_sat_mC = NX_FDD_ABSENT
124 rd.evap_split_mC = NX_FDD_ABSENT
125 rd.cond_over_ambient_mC = NX_FDD_ABSENT
126 rd.cfm_per_ton = NX_FDD_ABSENT
127 rd.cph_x10 = NX_FDD_ABSENT
128 nx_ac_fdd_analyze(rd, th, o)
129 if o.verdict != NX_FDD_INSUFFICIENT_DATA { return 90 }
130
131 // ===== PRECEDENCE: frozen coil beats a charge fault =============
132 fdd_reset_in(rd)
133 rd.suction_sat_mC = -2000
134 rd.superheat_mC = 17000
135 rd.subcool_mC = 11000
136 nx_ac_fdd_analyze(rd, th, o)
137 if o.verdict != NX_FDD_FROZEN_COIL { return 100 }
138
139 // ===== LIAR-KILL: verdict tracks subcooling =====================
140 // identical high superheat; only subcooling changes -> the diagnosis
141 // must change (undercharge vs liquid restriction), not stay constant.
142 fdd_reset_in(rd)
143 rd.superheat_mC = 17000
144 rd.subcool_mC = 1500
145 nx_ac_fdd_analyze(rd, th, o)
146 let v_low_sc: i64 = o.verdict
147 fdd_reset_in(rd)
148 rd.superheat_mC = 17000
149 rd.subcool_mC = 11000
150 nx_ac_fdd_analyze(rd, th, o)
151 let v_high_sc: i64 = o.verdict
152 if v_low_sc != NX_FDD_UNDERCHARGE { return 110 }
153 if v_high_sc != NX_FDD_LIQUID_RESTRICTION { return 111 }
154 if v_low_sc == v_high_sc { return 112 }
155
156 return 0
157}