code wiki / (root) / nx_maint_ingest.nx

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}