use serde_json::json;
use tokio_tungstenite::tungstenite::protocol::Message;
use crate::error::{validate_pin, ObnizError, ObnizResult};
use crate::obniz::Obniz;
#[derive(Debug, Clone)]
pub struct AdConfig {
pub stream: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AdValue {
pub channel: u8,
pub voltage: f64,
}
#[derive(Debug)]
pub struct AdChannel {
channel: u8,
obniz: Obniz,
}
impl AdChannel {
pub fn new(channel: u8, obniz: Obniz) -> Self {
Self { channel, obniz }
}
pub fn channel_key(&self) -> String {
format!("ad{}", self.channel)
}
pub async fn get(&self) -> ObnizResult<f64> {
validate_pin(self.channel)?;
let channel_key = self.channel_key();
let request = json!([{&channel_key: "get"}]);
let message = Message::from(request.to_string());
let response = self
.obniz
.send_await_response(message, channel_key.clone())
.await
.map_err(|e| ObnizError::Connection(e.to_string()))?;
if let Some(array) = response.as_array() {
if let Some(first_item) = array.first() {
if let Some(value) = first_item.get(&channel_key) {
return value.as_f64().ok_or_else(|| {
ObnizError::Generic("Invalid voltage value in response".to_string())
});
}
}
}
if let Some(value) = response.get(&channel_key) {
value
.as_f64()
.ok_or_else(|| ObnizError::Generic("Invalid voltage value in response".to_string()))
} else {
Err(ObnizError::Generic(format!(
"No response for AD channel {}",
self.channel
)))
}
}
pub async fn configure(&self, config: AdConfig) -> ObnizResult<()> {
validate_pin(self.channel)?;
let channel_key = self.channel_key();
let request = json!([{&channel_key: {"stream": config.stream}}]);
let message = Message::from(request.to_string());
self.obniz
.send_message(message)
.map_err(|e| ObnizError::Connection(e.to_string()))
}
pub async fn enable_stream(&self) -> ObnizResult<()> {
self.configure(AdConfig { stream: true }).await
}
pub async fn disable_stream(&self) -> ObnizResult<()> {
self.configure(AdConfig { stream: false }).await
}
pub async fn on_change<F>(&self, callback: F) -> ObnizResult<()>
where
F: Fn(f64) + Send + Sync + 'static,
{
validate_pin(self.channel)?;
self.enable_stream().await?;
let channel_key = self.channel_key();
let channel_key_clone = channel_key.clone();
self.obniz
.register_callback(channel_key, move |response| {
if let Some(value) = response.get(&channel_key_clone) {
if let Some(voltage) = value.as_f64() {
callback(voltage);
}
}
})
.map_err(|e| ObnizError::CallbackError(e.to_string()))?;
Ok(())
}
pub fn remove_callback(&self) -> ObnizResult<()> {
validate_pin(self.channel)?;
let channel_key = self.channel_key();
self.obniz
.unregister_callback(channel_key)
.map_err(|e| ObnizError::CallbackError(e.to_string()))
}
pub async fn deinit(&self) -> ObnizResult<()> {
validate_pin(self.channel)?;
let channel_key = self.channel_key();
let request = json!([{&channel_key: null}]);
let message = Message::from(request.to_string());
self.obniz
.send_message(message)
.map_err(|e| ObnizError::Connection(e.to_string()))
}
}
#[derive(Debug, Clone)]
pub struct AdManager {
obniz: Obniz,
}
impl AdManager {
pub fn new(obniz: Obniz) -> Self {
Self { obniz }
}
pub fn channel(&self, channel: u8) -> ObnizResult<AdChannel> {
validate_pin(channel)?;
Ok(AdChannel::new(channel, self.obniz.clone()))
}
pub async fn get_voltage(&self, channel: u8) -> ObnizResult<f64> {
self.channel(channel)?.get().await
}
pub async fn get_voltages(&self, channels: Vec<u8>) -> ObnizResult<Vec<AdValue>> {
let mut results = Vec::new();
for channel in channels {
let voltage = self.get_voltage(channel).await?;
results.push(AdValue { channel, voltage });
}
Ok(results)
}
pub async fn enable_channel_stream(&self, channel: u8) -> ObnizResult<()> {
self.channel(channel)?.enable_stream().await
}
pub async fn disable_channel_stream(&self, channel: u8) -> ObnizResult<()> {
self.channel(channel)?.disable_stream().await
}
pub async fn set_channel_callback<F>(&self, channel: u8, callback: F) -> ObnizResult<()>
where
F: Fn(f64) + Send + Sync + 'static,
{
self.channel(channel)?.on_change(callback).await
}
pub fn remove_channel_callback(&self, channel: u8) -> ObnizResult<()> {
self.channel(channel)?.remove_callback()
}
pub async fn deinit_channel(&self, channel: u8) -> ObnizResult<()> {
self.channel(channel)?.deinit().await
}
pub async fn deinit_all(&self) -> ObnizResult<()> {
for channel in 0..=11 {
if let Err(e) = self.deinit_channel(channel).await {
eprintln!("Failed to deinitialize AD channel {channel}: {e}");
}
}
Ok(())
}
pub async fn read_all(&self) -> ObnizResult<Vec<AdValue>> {
let channels: Vec<u8> = (0..=11).collect();
self.get_voltages(channels).await
}
pub fn voltage_to_percentage(voltage: f64) -> f64 {
(voltage / 5.0 * 100.0).clamp(0.0, 100.0)
}
pub fn is_voltage_safe(voltage: f64) -> bool {
(0.0..=5.0).contains(&voltage)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ad_value_creation() {
let value = AdValue {
channel: 5,
voltage: 3.3,
};
assert_eq!(value.channel, 5);
assert_eq!(value.voltage, 3.3);
}
#[test]
fn test_ad_config_creation() {
let config = AdConfig { stream: true };
assert!(config.stream);
let config = AdConfig { stream: false };
assert!(!config.stream);
}
#[test]
fn test_voltage_to_percentage() {
assert_eq!(AdManager::voltage_to_percentage(0.0), 0.0);
assert_eq!(AdManager::voltage_to_percentage(2.5), 50.0);
assert_eq!(AdManager::voltage_to_percentage(5.0), 100.0);
assert_eq!(AdManager::voltage_to_percentage(6.0), 100.0); }
#[test]
fn test_voltage_safety_check() {
assert!(AdManager::is_voltage_safe(0.0));
assert!(AdManager::is_voltage_safe(3.3));
assert!(AdManager::is_voltage_safe(5.0));
assert!(!AdManager::is_voltage_safe(-0.1));
assert!(!AdManager::is_voltage_safe(5.1));
}
#[test]
fn test_channel_key_generation() {
assert_eq!(format!("ad{}", 0), "ad0");
assert_eq!(format!("ad{}", 11), "ad11");
}
}