use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio_tungstenite::tungstenite::protocol::Message;
use crate::error::{ObnizError, ObnizResult};
use crate::obniz::Obniz;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ModulationType {
Am, }
#[derive(Debug, Clone)]
pub struct PwmConfig {
pub io_pin: u8,
pub frequency: u32,
pub pulse_width_ms: f64,
}
#[derive(Debug, Clone)]
pub struct ModulationConfig {
pub modulation_type: ModulationType,
pub symbol_length_ms: f64,
pub data: Vec<u8>,
}
#[derive(Debug)]
pub struct PwmChannel {
channel: u8,
obniz: Obniz,
}
impl PwmChannel {
pub fn new(channel: u8, obniz: Obniz) -> Self {
Self { channel, obniz }
}
pub fn channel_key(&self) -> String {
format!("pwm{}", self.channel)
}
pub async fn init(&self, io_pin: u8) -> ObnizResult<()> {
if self.channel > 5 {
return Err(ObnizError::Generic("PWM channel must be 0-5".to_string()));
}
if io_pin > 11 {
return Err(ObnizError::InvalidPin(io_pin));
}
let channel_key = self.channel_key();
let request = json!([{&channel_key: {"io": io_pin}}]);
let message = Message::from(request.to_string());
self.obniz
.send_message(message)
.map_err(|e| ObnizError::Connection(e.to_string()))
}
pub async fn set_frequency(&self, frequency: u32) -> ObnizResult<()> {
if frequency == 0 || frequency > 80_000_000 {
return Err(ObnizError::Generic(
"Frequency must be between 1 and 80,000,000 Hz".to_string(),
));
}
let channel_key = self.channel_key();
let request = json!([{&channel_key: {"freq": frequency}}]);
let message = Message::from(request.to_string());
self.obniz
.send_message(message)
.map_err(|e| ObnizError::Connection(e.to_string()))
}
pub async fn set_pulse_width(&self, pulse_width_ms: f64) -> ObnizResult<()> {
if pulse_width_ms < 0.0 {
return Err(ObnizError::Generic("Pulse width must be >= 0".to_string()));
}
let channel_key = self.channel_key();
let request = json!([{&channel_key: {"pulse": pulse_width_ms}}]);
let message = Message::from(request.to_string());
self.obniz
.send_message(message)
.map_err(|e| ObnizError::Connection(e.to_string()))
}
pub async fn set_duty_cycle(&self, frequency: u32, duty_percent: f64) -> ObnizResult<()> {
if !(0.0..=100.0).contains(&duty_percent) {
return Err(ObnizError::Generic(
"Duty cycle must be between 0 and 100%".to_string(),
));
}
let period_ms = 1000.0 / frequency as f64;
let pulse_width_ms = period_ms * duty_percent / 100.0;
self.set_frequency(frequency).await?;
self.set_pulse_width(pulse_width_ms).await
}
pub async fn configure(&self, config: PwmConfig) -> ObnizResult<()> {
self.init(config.io_pin).await?;
self.set_frequency(config.frequency).await?;
self.set_pulse_width(config.pulse_width_ms).await
}
pub async fn modulate(&self, config: ModulationConfig) -> ObnizResult<()> {
if config.symbol_length_ms < 0.05 || config.symbol_length_ms > 1000.0 {
return Err(ObnizError::Generic(
"Symbol length must be between 0.05 and 1000 ms".to_string(),
));
}
if config.data.is_empty() {
return Err(ObnizError::Generic(
"Modulation data cannot be empty".to_string(),
));
}
for &value in &config.data {
if value > 1 {
return Err(ObnizError::Generic(
"Modulation data must contain only 0 and 1".to_string(),
));
}
}
let channel_key = self.channel_key();
let request = json!([{&channel_key: {
"modulate": {
"type": config.modulation_type,
"symbol_length": config.symbol_length_ms,
"data": config.data
}
}}]);
let message = Message::from(request.to_string());
self.obniz
.send_message(message)
.map_err(|e| ObnizError::Connection(e.to_string()))
}
pub async fn square_wave(&self, io_pin: u8, frequency: u32) -> ObnizResult<()> {
let config = PwmConfig {
io_pin,
frequency,
pulse_width_ms: 500.0 / frequency as f64, };
self.configure(config).await
}
pub async fn servo(&self, io_pin: u8, angle: f64) -> ObnizResult<()> {
if !(0.0..=180.0).contains(&angle) {
return Err(ObnizError::Generic(
"Servo angle must be between 0 and 180 degrees".to_string(),
));
}
let pulse_width_ms = 1.0 + (angle / 180.0);
let config = PwmConfig {
io_pin,
frequency: 50, pulse_width_ms,
};
self.configure(config).await
}
pub async fn deinit(&self) -> ObnizResult<()> {
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 PwmManager {
obniz: Obniz,
}
impl PwmManager {
pub fn new(obniz: Obniz) -> Self {
Self { obniz }
}
pub fn channel(&self, channel: u8) -> ObnizResult<PwmChannel> {
if channel > 5 {
return Err(ObnizError::Generic("PWM channel must be 0-5".to_string()));
}
Ok(PwmChannel::new(channel, self.obniz.clone()))
}
pub async fn configure_channel(&self, channel: u8, config: PwmConfig) -> ObnizResult<()> {
self.channel(channel)?.configure(config).await
}
pub async fn set_channel_frequency(&self, channel: u8, frequency: u32) -> ObnizResult<()> {
self.channel(channel)?.set_frequency(frequency).await
}
pub async fn set_channel_pulse_width(
&self,
channel: u8,
pulse_width_ms: f64,
) -> ObnizResult<()> {
self.channel(channel)?.set_pulse_width(pulse_width_ms).await
}
pub async fn set_channel_duty_cycle(
&self,
channel: u8,
frequency: u32,
duty_percent: f64,
) -> ObnizResult<()> {
self.channel(channel)?
.set_duty_cycle(frequency, duty_percent)
.await
}
pub async fn square_wave(&self, channel: u8, io_pin: u8, frequency: u32) -> ObnizResult<()> {
self.channel(channel)?.square_wave(io_pin, frequency).await
}
pub async fn servo(&self, channel: u8, io_pin: u8, angle: f64) -> ObnizResult<()> {
self.channel(channel)?.servo(io_pin, angle).await
}
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..=5 {
if let Err(e) = self.deinit_channel(channel).await {
eprintln!("Failed to deinitialize PWM channel {channel}: {e}");
}
}
Ok(())
}
pub fn duty_cycle_to_pulse_width(frequency: u32, duty_percent: f64) -> f64 {
let period_ms = 1000.0 / frequency as f64;
period_ms * duty_percent / 100.0
}
pub fn pulse_width_to_duty_cycle(frequency: u32, pulse_width_ms: f64) -> f64 {
let period_ms = 1000.0 / frequency as f64;
(pulse_width_ms / period_ms * 100.0).clamp(0.0, 100.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pwm_config_creation() {
let config = PwmConfig {
io_pin: 5,
frequency: 1000,
pulse_width_ms: 0.5,
};
assert_eq!(config.io_pin, 5);
assert_eq!(config.frequency, 1000);
assert_eq!(config.pulse_width_ms, 0.5);
}
#[test]
fn test_modulation_config_creation() {
let config = ModulationConfig {
modulation_type: ModulationType::Am,
symbol_length_ms: 100.0,
data: vec![0, 1, 1, 0],
};
assert_eq!(config.modulation_type, ModulationType::Am);
assert_eq!(config.symbol_length_ms, 100.0);
assert_eq!(config.data, vec![0, 1, 1, 0]);
}
#[test]
fn test_duty_cycle_calculations() {
let pulse_width = PwmManager::duty_cycle_to_pulse_width(1000, 50.0);
assert_eq!(pulse_width, 0.5);
let duty_cycle = PwmManager::pulse_width_to_duty_cycle(1000, 0.25);
assert_eq!(duty_cycle, 25.0); }
#[test]
fn test_modulation_type_serialization() {
use serde_json;
let mod_type = ModulationType::Am;
let serialized = serde_json::to_string(&mod_type).unwrap();
assert_eq!(serialized, "\"am\"");
}
#[test]
fn test_channel_key_generation() {
assert_eq!(format!("pwm{}", 0), "pwm0");
assert_eq!(format!("pwm{}", 5), "pwm5");
}
}