nx_print_loop_e2e_test.nx source
↩ module page · 164 lines · 6017 B
1// nx_print_loop_e2e_test.nx -- end-to-end substrate loop:
2// simulated telemetry -> pump -> verdict -> response wire bytes.
3//
4// Proves the full substrate-level closure:
5//
6// telemetry_event (simulated; in prod from Moonraker JSON)
7// |
8// v
9// nx_runtime_monitor_pump_telemetry() (input side, shipped)
10// |
11// v
12// issue verdict (NX_RT_ISSUE_*)
13// |
14// v
15// nx_print_build_response_for_issue() (output side, shipped)
16// |
17// v
18// HTTP POST bytes ready for nx_mr_post_ipv4 (network shipped)
19//
20// Three scenarios per simulated print:
21//
22// A. Quiet tick (no issue) -> dispatcher returns 0 bytes
23// B. PAUSE-class issue (BED_TEMP_DROP) -> wire bytes contain
24// "POST /printer/print/pause"
25// C. HARD_STOP issue (LAYER_SHIFT) -> wire bytes contain
26// "POST /printer/print/cancel"
27//
28// expect_exit: 0
29// license_tier: ORIGINAL
30
31import "nx_syscalls.nx"
32import "nx_print_runtime_monitor.nx"
33import "nx_print_telemetry.nx"
34import "nx_print_response.nx"
35
36const Q14: i64 = 16384
37
38func contains_substring(hay: *u8, hay_len: i64, needle: *u8) -> i64 {
39 var n_len: i64 = 0
40 while needle[n_len] != 0 { n_len = n_len + 1 }
41 if n_len > hay_len { return 0 }
42 var i: i64 = 0
43 while i <= hay_len - n_len {
44 var j: i64 = 0
45 var ok: i64 = 1
46 while j < n_len {
47 if hay[i + j] != needle[j] { ok = 0; j = n_len }
48 j = j + 1
49 }
50 if ok == 1 { return 1 }
51 i = i + 1
52 }
53 return 0
54}
55
56func main() -> i64 {
57 let m: *NxRuntimeMonitor = nx_runtime_monitor_new(5000, 3, 5 * Q14, 2 * Q14)
58 if (m as i64) == 0 { return 5 }
59 nx_runtime_monitor_set_thermal(m, 2, 2000)
60 nx_runtime_monitor_set_layer_shift_tolerance(m, (3 * Q14) / 10)
61
62 let host: *u8 = "192.168.1.123:7125"
63 let host_len: i64 = 18
64 let buf: *u8 = sys_mmap(2048)
65
66 // Prime monitor with first event so subsequent ticks have last_z to diff
67 let prime: *NxPrintTelemetry = nx_print_telemetry_new()
68 prime.event_time_ms = 0
69 prime.has_motion = 1
70 prime.bed_actual_c = 60
71 prime.bed_setpoint_c = 60
72 prime.hotend_actual_c = 210
73 prime.hotend_setpoint_c = 210
74 prime.chamber_setpoint_c = -1
75 prime.has_thermal = 1
76 nx_runtime_monitor_pump_telemetry(m, prime)
77
78 // ===== Scenario A: Quiet tick -- no issue, no response bytes =====
79 let evA: *NxPrintTelemetry = nx_print_telemetry_new()
80 evA.event_time_ms = 5000
81 evA.z_q14 = Q14 / 5
82 evA.e_q14 = 10 * Q14
83 evA.xy_delta_q14 = 50 * Q14
84 evA.has_motion = 1
85 evA.bed_actual_c = 60
86 evA.bed_setpoint_c = 60
87 evA.hotend_actual_c = 210
88 evA.hotend_setpoint_c = 210
89 evA.chamber_setpoint_c = -1
90 evA.has_thermal = 1
91 let vA: i64 = nx_runtime_monitor_pump_telemetry(m, evA)
92 if vA != NX_RT_ISSUE_NONE { return 10 }
93 let nA: i64 = nx_print_build_response_for_issue(
94 vA, host, host_len, 0 as *u8, 0, buf, 2048)
95 if nA != 0 { return 11 }
96
97 // ===== Scenario B: BED_TEMP_DROP -> PAUSE =====
98 let evB1: *NxPrintTelemetry = nx_print_telemetry_new()
99 evB1.event_time_ms = 7000
100 evB1.bed_actual_c = 55
101 evB1.bed_setpoint_c = 60
102 evB1.hotend_actual_c = 210
103 evB1.hotend_setpoint_c = 210
104 evB1.chamber_setpoint_c = -1
105 evB1.has_thermal = 1
106 nx_runtime_monitor_pump_telemetry(m, evB1) // start timer
107 let evB2: *NxPrintTelemetry = nx_print_telemetry_new()
108 evB2.event_time_ms = 9500
109 evB2.bed_actual_c = 55
110 evB2.bed_setpoint_c = 60
111 evB2.hotend_actual_c = 210
112 evB2.hotend_setpoint_c = 210
113 evB2.chamber_setpoint_c = -1
114 evB2.has_thermal = 1
115 let vB: i64 = nx_runtime_monitor_pump_telemetry(m, evB2)
116 if vB != NX_RT_ISSUE_BED_TEMP_DROP { return 20 }
117 let nB: i64 = nx_print_build_response_for_issue(
118 vB, host, host_len, 0 as *u8, 0, buf, 2048)
119 if nB <= 0 { return 21 }
120 if contains_substring(buf, nB, "POST /printer/print/pause") != 1 { return 22 }
121
122 // ===== Scenario C: LAYER_SHIFT -> HARD_STOP (cancel) =====
123 let evC: *NxPrintTelemetry = nx_print_telemetry_new()
124 evC.event_time_ms = 10000
125 evC.commanded_x_q14 = 100 * Q14
126 evC.actual_x_q14 = 100 * Q14 - Q14 // 1mm drift > 0.3mm tol
127 evC.commanded_y_q14 = 100 * Q14
128 evC.actual_y_q14 = 100 * Q14
129 evC.has_shift = 1
130 let vC: i64 = nx_runtime_monitor_pump_telemetry(m, evC)
131 if vC != NX_RT_ISSUE_LAYER_SHIFT { return 30 }
132 let nC: i64 = nx_print_build_response_for_issue(
133 vC, host, host_len, 0 as *u8, 0, buf, 2048)
134 if nC <= 0 { return 31 }
135 if contains_substring(buf, nC, "POST /printer/print/cancel") != 1 { return 32 }
136
137 // ===== Scenario D: CHAMBER_TEMP_DROP -> NOTIFY (no bytes) =====
138 let evD1: *NxPrintTelemetry = nx_print_telemetry_new()
139 evD1.event_time_ms = 11000
140 evD1.bed_actual_c = 60
141 evD1.bed_setpoint_c = 60
142 evD1.hotend_actual_c = 210
143 evD1.hotend_setpoint_c = 210
144 evD1.chamber_actual_c = 25
145 evD1.chamber_setpoint_c = 30
146 evD1.has_thermal = 1
147 nx_runtime_monitor_pump_telemetry(m, evD1) // start chamber timer
148 let evD2: *NxPrintTelemetry = nx_print_telemetry_new()
149 evD2.event_time_ms = 13500
150 evD2.bed_actual_c = 60
151 evD2.bed_setpoint_c = 60
152 evD2.hotend_actual_c = 210
153 evD2.hotend_setpoint_c = 210
154 evD2.chamber_actual_c = 25
155 evD2.chamber_setpoint_c = 30
156 evD2.has_thermal = 1
157 let vD: i64 = nx_runtime_monitor_pump_telemetry(m, evD2)
158 if vD != NX_RT_ISSUE_CHAMBER_TEMP_DROP { return 40 }
159 let nD: i64 = nx_print_build_response_for_issue(
160 vD, host, host_len, 0 as *u8, 0, buf, 2048)
161 if nD != 0 { return 41 } // NOTIFY-only
162
163 return 0
164}