nx_maint_ingest.nx source
↩ module page · 126 lines · 4914 B
1// nx_maint_ingest.nx -- the HARDWARE-facing ingest rung (R1) of the maintenance
2// platform. Turns a raw sensor reading from ANY source into the normalized,
3// typed sample the physics kernels consume, and routes each sensor KIND to its
4// kernel. This is the rung the 5 kernels were floating above -- it closes the
5// hardware -> kernel path (R0 driver -> R1 ingest -> R2 kernel).
6//
7// Operator (2026-06-23): build the platform "from the hardware rung up, each
8// rung." Sensing doctrine (operator-chosen): sovereign-DIY PREFERRED, support
9// any source (Emporia/Sense/OBD/manual) vendor-agnostically; UNKNOWN is
10// first-class (recorded for later, never dropped) -- the IoT-brain doctrine.
11//
12// NO-FLOAT: normalization is a DATA-DRIVEN affine transform (scale_num/scale_den
13// + offset live in the source config #11, not hardcoded per-vendor guesses --
14// honest: we don't invent vendor wire formats; the per-vendor DRIVER (R0)
15// supplies the raw value + its scale).
16//
17// NEVER-BRICK (#26): reads a value, normalizes, routes; writes nothing,
18// commands no sensor. Read-only by construction.
19//
20// genealogy_id: project-maintenance-platform-sclass-2026-06-23 (R1 hardware-ingest rung)
21// license_tier: ORIGINAL
22//
23// nx_capability_claims:
24// needs: [pointer_arithmetic]
25// provides: [vendor_agnostic_sensor_ingest, kind_to_kernel_routing,
26// data_driven_normalize, unknown_sensor_first_class]
27// safety: [no_floating_point, no_syscall, bounded_iteration,
28// read_only_no_device_write, no_firmware_write_by_construction,
29// sealed_enum]
30// verdict: [sealed_enum, no_silent_failure, unknown_recorded_not_dropped]
31// license: ORIGINAL
32// kind: maintenance_runtime_primitive
33
34// ---- Sensor kinds (what the sensor measures) -----------------------
35const NX_SENSOR_UNKNOWN: i64 = 0
36const NX_SENSOR_TEMP: i64 = 1
37const NX_SENSOR_CURRENT: i64 = 2
38const NX_SENSOR_VOLTAGE: i64 = 3
39const NX_SENSOR_VIBRATION: i64 = 4
40const NX_SENSOR_PRESSURE: i64 = 5
41const NX_SENSOR_FLOW: i64 = 6
42const NX_SENSOR_RUNTIME: i64 = 7
43const NX_SENSOR_USAGE: i64 = 8
44const NX_SENSOR_N: i64 = 9
45
46// ---- Sources (sovereign-DIY preferred; support all) ----------------
47const NX_SRC_UNKNOWN: i64 = 0
48const NX_SRC_SOVEREIGN_DIY: i64 = 1
49const NX_SRC_EMPORIA: i64 = 2
50const NX_SRC_SENSE: i64 = 3
51const NX_SRC_OBD: i64 = 4
52const NX_SRC_MANUAL: i64 = 5
53const NX_SRC_N: i64 = 6
54
55// ---- Kernel targets (which physics kernel consumes the sample) -----
56const NX_MK_THERMAL: i64 = 0
57const NX_MK_ROTATING: i64 = 1
58const NX_MK_CONSUMABLE: i64 = 2
59const NX_MK_FLUID: i64 = 3
60const NX_MK_ELECTRICAL: i64 = 4
61const NX_MK_NONE: i64 = 9 // unrecognized kind -> no kernel (first-class)
62
63func nx_sensor_kind_is_valid(k: i64) -> i64 {
64 if k < 0 { return 0 }
65 if k >= NX_SENSOR_N { return 0 }
66 return 1
67}
68func nx_src_is_valid(s: i64) -> i64 {
69 if s < 0 { return 0 }
70 if s >= NX_SRC_N { return 0 }
71 return 1
72}
73
74// A raw sensor reading + its data-driven normalization (from the R0 driver).
75struct SensorReading {
76 kind: i64, // NX_SENSOR_*
77 src: i64, // NX_SRC_*
78 raw_value: i64, // native units from the driver
79 scale_num: i64, // canonical = raw*scale_num/scale_den + offset
80 scale_den: i64,
81 offset: i64,
82}
83
84// The normalized sample, ready for its kernel.
85struct NormSample {
86 kind: i64,
87 kernel: i64, // NX_MK_* (NONE if the kind has no kernel yet)
88 value: i64, // canonical value (mC / centi-A / centi-V / um/s / centi-PSI / ...)
89 recognized: i64, // 1 = routed to a kernel; 0 = UNKNOWN kind, RECORDED for later
90}
91
92// Route a sensor kind to the kernel that consumes it.
93func nx_maint_sensor_kernel(kind: i64) -> i64 {
94 if kind == NX_SENSOR_TEMP { return NX_MK_THERMAL }
95 if kind == NX_SENSOR_RUNTIME { return NX_MK_THERMAL }
96 if kind == NX_SENSOR_CURRENT { return NX_MK_ELECTRICAL }
97 if kind == NX_SENSOR_VOLTAGE { return NX_MK_ELECTRICAL }
98 if kind == NX_SENSOR_VIBRATION { return NX_MK_ROTATING }
99 if kind == NX_SENSOR_PRESSURE { return NX_MK_FLUID }
100 if kind == NX_SENSOR_FLOW { return NX_MK_FLUID }
101 if kind == NX_SENSOR_USAGE { return NX_MK_CONSUMABLE }
102 return NX_MK_NONE
103}
104
105// Ingest one reading -> normalized sample. Returns 1 (routed), 0 (UNKNOWN kind,
106// recorded first-class), or -2 (bad arg: cannot normalize).
107func nx_maint_ingest(r: *SensorReading, out: *NormSample) -> i64 {
108 out.kind = r.kind
109 out.kernel = NX_MK_NONE
110 out.value = 0
111 out.recognized = 0
112
113 if r.scale_den == 0 {
114 return 0 - 2
115 }
116
117 out.value = (r.raw_value * r.scale_num) / r.scale_den + r.offset
118 let k: i64 = nx_maint_sensor_kernel(r.kind)
119 out.kernel = k
120 if k == NX_MK_NONE {
121 out.recognized = 0
122 return 0
123 }
124 out.recognized = 1
125 return 1
126}