File size: 3,186 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
// 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"));
    }
}