Robbo
Initial: WASM sensor stack β€” core types, MCU module (32KB), WIT contract
7932636 unverified
// Synapse Agriculture β€” Build a Farm Web Frontend
//
// This crate compiles to WASM and runs in the browser.
// It shares synapse-core types with the sensor modules,
// meaning the same Reading/TransmissionPayload types that
// the MCU produces are what the dashboard renders.
//
// For now this is a stub proving the shared type import works.
// Leptos UI comes next once the core+sensor crates stabilize.
use synapse_core::{
MeasurementUnit, Reading, ReadingQuality, TransmissionPayload,
};
/// Demonstrate that synapse-core types work in std/browser context.
/// This will become the data layer for the Leptos dashboard.
pub fn decode_payload(cbor_bytes: &[u8]) -> Result<TransmissionPayload, String> {
minicbor::decode(cbor_bytes).map_err(|e| format!("CBOR decode error: {e}"))
}
/// Format a reading for dashboard display.
/// Uses the std-only helpers from synapse-core (calibrated_f64, unit_str).
pub fn format_reading(reading: &Reading) -> String {
let quality_indicator = match reading.quality {
ReadingQuality::Good => "",
ReadingQuality::Degraded => " ⚠",
ReadingQuality::CalNeeded => " πŸ”§",
ReadingQuality::Fault => " ❌",
};
format!(
"{:.2} {}{}",
reading.calibrated_f64(),
reading.unit_str(),
quality_indicator
)
}
#[cfg(test)]
mod tests {
use super::*;
use synapse_core::Calibration;
#[test]
fn format_ph_reading() {
let r = Reading {
timestamp_ms: 1712345678000,
channel: 0,
raw_value: 1650,
calibrated_value: 7230,
unit: MeasurementUnit::Ph,
quality: ReadingQuality::Good,
};
let s = format_reading(&r);
assert!(s.contains("7.23"), "expected '7.23' in '{s}'");
assert!(s.contains("pH"), "expected 'pH' in '{s}'");
}
#[test]
fn format_fault_reading() {
let r = Reading {
timestamp_ms: 0,
channel: 0,
raw_value: 0,
calibrated_value: 0,
unit: MeasurementUnit::DissolvedOxygen,
quality: ReadingQuality::Fault,
};
let s = format_reading(&r);
assert!(s.contains("❌"), "expected fault indicator in '{s}'");
}
#[test]
fn cbor_roundtrip_from_simulated_node() {
// Simulate what a sensor node would transmit
let payload = TransmissionPayload {
node_id: 1,
sequence: 0,
battery_mv: 3700,
readings: vec![Reading {
timestamp_ms: 1712345678000,
channel: 0,
raw_value: 1650,
calibrated_value: 7230,
unit: MeasurementUnit::Ph,
quality: ReadingQuality::Good,
}],
};
// Encode (as the MCU would)
let mut buf = Vec::new();
minicbor::encode(&payload, &mut buf).unwrap();
// Decode (as the browser would)
let decoded = decode_payload(&buf).unwrap();
assert_eq!(decoded.node_id, 1);
let formatted = format_reading(&decoded.readings[0]);
assert!(formatted.contains("7.23"));
}
}