File size: 7,066 Bytes
7932636 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | // Synapse Agriculture — Sensor Module Interface
// This WIT definition is the IMMORTAL CONTRACT between:
// - Guest: sensor WASM modules (runs on MCU/gateway/host)
// - Host: wasm3 (MCU), wasmtime (gateway/host), browser VM
//
// Adding a new sensor type = add to this file, regen bindings,
// type-check catches every integration point at compile time.
//
// Changing this file is a BREAKING CHANGE across the entire stack.
// Treat it like a database migration — versioned, reviewed, irreversible.
package synapse:sensor@0.1.0;
/// Types shared across all sensor modules and host runtimes.
/// These compile into synapse-core and are used everywhere.
interface types {
/// Sensor reading with metadata for provenance tracking
record reading {
/// Unix timestamp in milliseconds (from host clock or RTC)
timestamp-ms: u64,
/// Sensor channel identifier (maps to physical probe)
channel: u8,
/// Raw ADC or digital value before calibration
raw-value: s32,
/// Calibrated value as fixed-point (value * 1000)
/// Using s32 instead of f32 because wasm3 soft-float
/// on Cortex-M is slow and we don't need the precision
calibrated-value: s32,
/// Unit of measurement after calibration
unit: measurement-unit,
/// Quality/confidence flag from self-diagnostics
quality: reading-quality,
}
/// Fixed-point calibration coefficients for linear cal:
/// calibrated = (raw * slope / 1000) + (offset / 1000)
/// Two-point cal: derive slope/offset from known standards
record calibration {
slope: s32, // multiplied by 1000
offset: s32, // multiplied by 1000
}
/// What physical quantity this reading represents
enum measurement-unit {
/// Water chemistry
ph, // pH units (0-14)
ec, // electrical conductivity, µS/cm
dissolved-oxygen, // mg/L
orp, // mV
temperature-water, // °C * 1000
/// Soil
moisture-vwc, // volumetric water content, % * 1000
temperature-soil, // °C * 1000
/// Atmosphere
temperature-air, // °C * 1000
humidity, // % * 1000
pressure, // hPa * 1000
light-lux, // lux
light-par, // µmol/m²/s (photosynthetically active)
/// Power (Layer 7)
voltage, // mV
current, // mA
power, // mW
battery-soc, // state of charge, % * 10
}
/// Self-diagnostic quality assessment
enum reading-quality {
good,
degraded, // reading taken but outside expected range
cal-needed, // calibration overdue or drift detected
fault, // sensor not responding or shorted
}
/// Configuration pushed from gateway to node
record sensor-config {
/// Sampling interval in seconds
sample-interval-secs: u32,
/// Which channels to read (bitmask, up to 8 channels)
active-channels: u8,
/// Per-channel calibration (indexed by channel number)
calibrations: list<calibration>,
}
/// Compact transmission payload for LoRa
/// Designed to fit in a single LoRa packet (<= 242 bytes at SF7)
record transmission-payload {
/// Node identifier (unique per site)
node-id: u16,
/// Sequence number for dedup and gap detection
sequence: u16,
/// Battery voltage in mV (for power monitoring)
battery-mv: u16,
/// All readings from this sample cycle
readings: list<reading>,
}
}
/// Host functions provided TO the sensor module BY the runtime.
/// The MCU firmware implements these against real hardware.
/// The test harness implements them as mocks.
/// The browser implements them as no-ops or simulations.
interface host {
use types.{reading, calibration};
/// Read raw value from I2C sensor
/// address: 7-bit I2C device address (e.g., 0x63 for Atlas pH)
/// register: register to read from
/// length: bytes to read (max 32)
/// Returns: raw bytes from device, or error
read-i2c: func(address: u8, register: u8, length: u8) -> result<list<u8>, sensor-error>;
/// Read ADC channel (for analog sensors)
/// channel: ADC channel number (0-7 on RP2350)
/// Returns: raw 12-bit ADC value (0-4095)
read-adc: func(channel: u8) -> result<u16, sensor-error>;
/// Get current timestamp from RTC or host clock
get-timestamp-ms: func() -> u64;
/// Queue a LoRa transmission
/// payload: CBOR-encoded bytes to transmit
/// Returns: number of bytes queued, or error
transmit: func(payload: list<u8>) -> result<u32, sensor-error>;
/// Enter low-power sleep for specified duration
/// The WASM module yields execution here; host handles
/// actual MCU sleep modes (DORMANT on RP2350)
sleep-ms: func(duration-ms: u32);
/// Log a diagnostic message (forwarded to gateway if possible)
/// Compiled out / no-op on MCU builds via feature flag
log: func(level: log-level, message: string);
enum sensor-error {
/// Device not responding on bus
not-found,
/// Bus arbitration failure
bus-error,
/// Device returned NAK
nak,
/// Read timed out
timeout,
/// Transmission queue full
queue-full,
/// Generic / unclassified
other,
}
enum log-level {
debug,
info,
warn,
error,
}
}
/// The interface that every sensor module MUST implement.
/// This is the guest-side contract — the "main" of the module.
interface guest {
use types.{reading, sensor-config, transmission-payload};
/// Called once at boot. Host passes stored config.
/// Module initializes internal state, validates config.
/// Returns: true if init succeeded, false to signal fault.
init: func(config: sensor-config) -> bool;
/// Called each sample cycle by the host's main loop.
/// Module reads sensors (via host.read-i2c / host.read-adc),
/// applies calibration, builds readings list.
/// Returns: payload ready for LoRa transmission.
sample: func() -> transmission-payload;
/// Called when gateway pushes new config (e.g., new cal values).
/// Module validates and applies, returns success/failure.
reconfigure: func(config: sensor-config) -> bool;
/// Self-diagnostic. Module checks sensor responsiveness,
/// validates readings against expected ranges, reports health.
/// Returns: list of (channel, quality) pairs.
diagnose: func() -> list<tuple<u8, reading-quality>>;
use host.{sensor-error};
/// Redeclare quality enum access for diagnose return
use types.{reading-quality};
}
/// The complete world — wires guest to host.
/// cargo-component uses this to generate the full bindings.
world sensor-node {
import host;
export guest;
}
|