Skip to main content

obniz_rust/
ad.rs

1// Serde traits may be used in future for more complex AD configurations
2use serde_json::json;
3use tokio_tungstenite::tungstenite::protocol::Message;
4
5use crate::error::{validate_pin, ObnizError, ObnizResult};
6use crate::obniz::Obniz;
7
8/// AD channel configuration
9#[derive(Debug, Clone)]
10pub struct AdConfig {
11    pub stream: bool,
12}
13
14/// AD measurement result
15#[derive(Debug, Clone, PartialEq)]
16pub struct AdValue {
17    pub channel: u8,
18    pub voltage: f64,
19}
20
21/// Individual AD channel controller
22#[derive(Debug)]
23pub struct AdChannel {
24    channel: u8,
25    obniz: Obniz,
26}
27
28impl AdChannel {
29    pub fn new(channel: u8, obniz: Obniz) -> Self {
30        Self { channel, obniz }
31    }
32
33    pub fn channel_key(&self) -> String {
34        format!("ad{}", self.channel)
35    }
36
37    /// Get current voltage reading
38    pub async fn get(&self) -> ObnizResult<f64> {
39        validate_pin(self.channel)?;
40
41        let channel_key = self.channel_key();
42        let request = json!([{&channel_key: "get"}]);
43        let message = Message::from(request.to_string());
44
45        let response = self
46            .obniz
47            .send_await_response(message, channel_key.clone())
48            .await
49            .map_err(|e| ObnizError::Connection(e.to_string()))?;
50
51        // Parse the response to extract the voltage value
52        // Response format is typically [{"ad2": 3.3}]
53        if let Some(array) = response.as_array() {
54            if let Some(first_item) = array.first() {
55                if let Some(value) = first_item.get(&channel_key) {
56                    return value.as_f64().ok_or_else(|| {
57                        ObnizError::Generic("Invalid voltage value in response".to_string())
58                    });
59                }
60            }
61        }
62
63        // Fallback: try direct object access
64        if let Some(value) = response.get(&channel_key) {
65            value
66                .as_f64()
67                .ok_or_else(|| ObnizError::Generic("Invalid voltage value in response".to_string()))
68        } else {
69            Err(ObnizError::Generic(format!(
70                "No response for AD channel {}",
71                self.channel
72            )))
73        }
74    }
75
76    /// Configure AD channel
77    pub async fn configure(&self, config: AdConfig) -> ObnizResult<()> {
78        validate_pin(self.channel)?;
79
80        let channel_key = self.channel_key();
81        let request = json!([{&channel_key: {"stream": config.stream}}]);
82        let message = Message::from(request.to_string());
83
84        self.obniz
85            .send_message(message)
86            .map_err(|e| ObnizError::Connection(e.to_string()))
87    }
88
89    /// Enable streaming mode
90    pub async fn enable_stream(&self) -> ObnizResult<()> {
91        self.configure(AdConfig { stream: true }).await
92    }
93
94    /// Disable streaming mode
95    pub async fn disable_stream(&self) -> ObnizResult<()> {
96        self.configure(AdConfig { stream: false }).await
97    }
98
99    /// Register callback for voltage changes (stream mode)
100    pub async fn on_change<F>(&self, callback: F) -> ObnizResult<()>
101    where
102        F: Fn(f64) + Send + Sync + 'static,
103    {
104        validate_pin(self.channel)?;
105
106        // Enable stream mode first
107        self.enable_stream().await?;
108
109        let channel_key = self.channel_key();
110        let channel_key_clone = channel_key.clone();
111
112        self.obniz
113            .register_callback(channel_key, move |response| {
114                if let Some(value) = response.get(&channel_key_clone) {
115                    if let Some(voltage) = value.as_f64() {
116                        callback(voltage);
117                    }
118                }
119            })
120            .map_err(|e| ObnizError::CallbackError(e.to_string()))?;
121
122        Ok(())
123    }
124
125    /// Remove callback for this channel
126    pub fn remove_callback(&self) -> ObnizResult<()> {
127        validate_pin(self.channel)?;
128        let channel_key = self.channel_key();
129        self.obniz
130            .unregister_callback(channel_key)
131            .map_err(|e| ObnizError::CallbackError(e.to_string()))
132    }
133
134    /// Deinitialize AD channel
135    pub async fn deinit(&self) -> ObnizResult<()> {
136        validate_pin(self.channel)?;
137
138        let channel_key = self.channel_key();
139        let request = json!([{&channel_key: null}]);
140        let message = Message::from(request.to_string());
141
142        self.obniz
143            .send_message(message)
144            .map_err(|e| ObnizError::Connection(e.to_string()))
145    }
146}
147
148/// AD manager for handling multiple channels
149#[derive(Debug, Clone)]
150pub struct AdManager {
151    obniz: Obniz,
152}
153
154impl AdManager {
155    pub fn new(obniz: Obniz) -> Self {
156        Self { obniz }
157    }
158
159    /// Get specific AD channel (0-11)
160    pub fn channel(&self, channel: u8) -> ObnizResult<AdChannel> {
161        validate_pin(channel)?;
162        Ok(AdChannel::new(channel, self.obniz.clone()))
163    }
164
165    /// Get voltage from specific channel
166    pub async fn get_voltage(&self, channel: u8) -> ObnizResult<f64> {
167        self.channel(channel)?.get().await
168    }
169
170    /// Get voltages from multiple channels
171    pub async fn get_voltages(&self, channels: Vec<u8>) -> ObnizResult<Vec<AdValue>> {
172        let mut results = Vec::new();
173
174        for channel in channels {
175            let voltage = self.get_voltage(channel).await?;
176            results.push(AdValue { channel, voltage });
177        }
178
179        Ok(results)
180    }
181
182    /// Enable streaming on specific channel
183    pub async fn enable_channel_stream(&self, channel: u8) -> ObnizResult<()> {
184        self.channel(channel)?.enable_stream().await
185    }
186
187    /// Disable streaming on specific channel
188    pub async fn disable_channel_stream(&self, channel: u8) -> ObnizResult<()> {
189        self.channel(channel)?.disable_stream().await
190    }
191
192    /// Set callback for specific channel
193    pub async fn set_channel_callback<F>(&self, channel: u8, callback: F) -> ObnizResult<()>
194    where
195        F: Fn(f64) + Send + Sync + 'static,
196    {
197        self.channel(channel)?.on_change(callback).await
198    }
199
200    /// Remove callback for specific channel
201    pub fn remove_channel_callback(&self, channel: u8) -> ObnizResult<()> {
202        self.channel(channel)?.remove_callback()
203    }
204
205    /// Deinitialize specific channel
206    pub async fn deinit_channel(&self, channel: u8) -> ObnizResult<()> {
207        self.channel(channel)?.deinit().await
208    }
209
210    /// Deinitialize all channels
211    pub async fn deinit_all(&self) -> ObnizResult<()> {
212        for channel in 0..=11 {
213            if let Err(e) = self.deinit_channel(channel).await {
214                eprintln!("Failed to deinitialize AD channel {channel}: {e}");
215            }
216        }
217        Ok(())
218    }
219
220    /// Get readings from all channels
221    pub async fn read_all(&self) -> ObnizResult<Vec<AdValue>> {
222        let channels: Vec<u8> = (0..=11).collect();
223        self.get_voltages(channels).await
224    }
225
226    /// Utility function to convert voltage to percentage (0V = 0%, 5V = 100%)
227    pub fn voltage_to_percentage(voltage: f64) -> f64 {
228        (voltage / 5.0 * 100.0).clamp(0.0, 100.0)
229    }
230
231    /// Utility function to check if voltage is within safe range
232    pub fn is_voltage_safe(voltage: f64) -> bool {
233        (0.0..=5.0).contains(&voltage)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_ad_value_creation() {
243        let value = AdValue {
244            channel: 5,
245            voltage: 3.3,
246        };
247
248        assert_eq!(value.channel, 5);
249        assert_eq!(value.voltage, 3.3);
250    }
251
252    #[test]
253    fn test_ad_config_creation() {
254        let config = AdConfig { stream: true };
255        assert!(config.stream);
256
257        let config = AdConfig { stream: false };
258        assert!(!config.stream);
259    }
260
261    #[test]
262    fn test_voltage_to_percentage() {
263        assert_eq!(AdManager::voltage_to_percentage(0.0), 0.0);
264        assert_eq!(AdManager::voltage_to_percentage(2.5), 50.0);
265        assert_eq!(AdManager::voltage_to_percentage(5.0), 100.0);
266        assert_eq!(AdManager::voltage_to_percentage(6.0), 100.0); // Clamped
267    }
268
269    #[test]
270    fn test_voltage_safety_check() {
271        assert!(AdManager::is_voltage_safe(0.0));
272        assert!(AdManager::is_voltage_safe(3.3));
273        assert!(AdManager::is_voltage_safe(5.0));
274        assert!(!AdManager::is_voltage_safe(-0.1));
275        assert!(!AdManager::is_voltage_safe(5.1));
276    }
277
278    #[test]
279    fn test_channel_key_generation() {
280        // We can't easily test the full AdChannel without Obniz instance
281        // but we can test the key generation logic
282        assert_eq!(format!("ad{}", 0), "ad0");
283        assert_eq!(format!("ad{}", 11), "ad11");
284    }
285}