nx_print_telemetry.nx source
↩ module page · 149 lines · 5135 B
1// nx_print_telemetry.nx -- unified telemetry-event bridge for
2// the print runtime monitor.
3//
4// Lets operator submit ONE event struct per telemetry tick
5// (from Moonraker JSON, serial-port read, fake source for
6// testing, etc.) and have it fanned into all 5 step* surfaces
7// on the runtime monitor.
8//
9// has_* validity masks let the operator populate only the
10// fields they actually have (e.g. Qidi without chamber heater
11// leaves has_thermal = 1 but chamber_setpoint_c = -1; an MCU
12// without mcu_position scaling leaves has_shift = 0).
13//
14// Pump returns the FIRST non-NONE verdict in priority order:
15// 1. LAYER_SHIFT (most critical mid-print)
16// 2. Z_BACKWARD (via step(), takes precedence over STUCK)
17// 3. STUCK_LAYER (via step())
18// 4. FLOW_ANOMALY (via step())
19// 5. EXTRUDER_RUNAWAY (via step())
20// 6. BED_TEMP_DROP (via step_thermal())
21// 7. HOTEND_TEMP_DROP (via step_thermal())
22// 8. CHAMBER_TEMP_DROP (via step_thermal())
23// 9. FILAMENT_RUNOUT (via step_runout())
24//
25// Note: step* surfaces independently increment n_issues_total,
26// so if multiple kinds fire on one tick, the count tracks every
27// fire even though the returned verdict is just the first one.
28//
29// license_tier: ORIGINAL
30
31import "nx_syscalls.nx"
32import "nx_print_runtime_monitor.nx"
33
34struct NxPrintTelemetry {
35 event_time_ms: i64,
36
37 // Motion (consumed by step())
38 z_q14: i64,
39 e_q14: i64,
40 xy_delta_q14: i64,
41
42 // Thermal (consumed by step_thermal())
43 bed_actual_c: i64,
44 bed_setpoint_c: i64,
45 hotend_actual_c: i64,
46 hotend_setpoint_c: i64,
47 chamber_actual_c: i64,
48 chamber_setpoint_c: i64,
49
50 // Material (consumed by step_runout())
51 runout_triggered: i64,
52
53 // Kinematics (consumed by step_layer_shift())
54 commanded_x_q14: i64,
55 actual_x_q14: i64,
56 commanded_y_q14: i64,
57 actual_y_q14: i64,
58
59 // Validity masks: 1 = corresponding step* should be called
60 has_motion: i64,
61 has_thermal: i64,
62 has_runout: i64,
63 has_shift: i64,
64}
65
66const NX_PRINT_TELEMETRY_BYTES: i64 = 152 // 19 fields × 8
67
68// Allocate + zero-init. All has_* default to 0 (no detection on
69// pump) so partial population is safe.
70func nx_print_telemetry_new() -> *NxPrintTelemetry {
71 let ev: *NxPrintTelemetry = (sys_mmap(NX_PRINT_TELEMETRY_BYTES)) as *NxPrintTelemetry
72 if (ev as i64) == 0 { return 0 as *NxPrintTelemetry }
73 ev.event_time_ms = 0
74 ev.z_q14 = 0
75 ev.e_q14 = 0
76 ev.xy_delta_q14 = 0
77 ev.bed_actual_c = 0
78 ev.bed_setpoint_c = -1
79 ev.hotend_actual_c = 0
80 ev.hotend_setpoint_c = -1
81 ev.chamber_actual_c = 0
82 ev.chamber_setpoint_c = -1
83 ev.runout_triggered = 0
84 ev.commanded_x_q14 = 0
85 ev.actual_x_q14 = 0
86 ev.commanded_y_q14 = 0
87 ev.actual_y_q14 = 0
88 ev.has_motion = 0
89 ev.has_thermal = 0
90 ev.has_runout = 0
91 ev.has_shift = 0
92 return ev
93}
94
95// Fan one telemetry event into all enabled step* surfaces.
96// Returns first non-NONE verdict in priority order (see top
97// comment). m.n_issues_total still tracks every fire.
98func nx_runtime_monitor_pump_telemetry(
99 m: *NxRuntimeMonitor,
100 ev: *NxPrintTelemetry
101) -> i64 {
102 if (m as i64) == 0 { return NX_RT_ISSUE_NONE }
103 if (ev as i64) == 0 { return NX_RT_ISSUE_NONE }
104
105 var first: i64 = NX_RT_ISSUE_NONE
106
107 // 1. Layer shift (highest priority -- catches lost-step cascade
108 // before any other detector can downstream-symptom on it).
109 if ev.has_shift != 0 {
110 let v_s: i64 = nx_runtime_monitor_step_layer_shift(m,
111 ev.commanded_x_q14, ev.actual_x_q14,
112 ev.commanded_y_q14, ev.actual_y_q14)
113 if v_s != NX_RT_ISSUE_NONE {
114 if first == NX_RT_ISSUE_NONE { first = v_s }
115 }
116 }
117
118 // 2. Motion (covers Z_BACKWARD, STUCK_LAYER, FLOW_ANOMALY,
119 // EXTRUDER_RUNAWAY with Z_BACKWARD precedence already in step()).
120 if ev.has_motion != 0 {
121 let v_m: i64 = nx_runtime_monitor_step(m,
122 ev.z_q14, ev.e_q14, ev.xy_delta_q14, ev.event_time_ms)
123 if v_m != NX_RT_ISSUE_NONE {
124 if first == NX_RT_ISSUE_NONE { first = v_m }
125 }
126 }
127
128 // 3. Thermal (BED -> HOTEND -> CHAMBER priority inside step_thermal).
129 if ev.has_thermal != 0 {
130 let v_t: i64 = nx_runtime_monitor_step_thermal(m,
131 ev.bed_actual_c, ev.bed_setpoint_c,
132 ev.hotend_actual_c, ev.hotend_setpoint_c,
133 ev.chamber_actual_c, ev.chamber_setpoint_c,
134 ev.event_time_ms)
135 if v_t != NX_RT_ISSUE_NONE {
136 if first == NX_RT_ISSUE_NONE { first = v_t }
137 }
138 }
139
140 // 4. Material (runout edge-detector).
141 if ev.has_runout != 0 {
142 let v_r: i64 = nx_runtime_monitor_step_runout(m, ev.runout_triggered)
143 if v_r != NX_RT_ISSUE_NONE {
144 if first == NX_RT_ISSUE_NONE { first = v_r }
145 }
146 }
147
148 return first
149}