1use serde::{Deserialize, Serialize};
2use serde_json::json;
3use tokio_tungstenite::tungstenite::protocol::Message;
4
5use crate::error::{ObnizError, ObnizResult};
6use crate::obniz::Obniz;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum ModulationType {
12 Am, }
14
15#[derive(Debug, Clone)]
17pub struct PwmConfig {
18 pub io_pin: u8,
19 pub frequency: u32,
20 pub pulse_width_ms: f64,
21}
22
23#[derive(Debug, Clone)]
25pub struct ModulationConfig {
26 pub modulation_type: ModulationType,
27 pub symbol_length_ms: f64,
28 pub data: Vec<u8>,
29}
30
31#[derive(Debug)]
33pub struct PwmChannel {
34 channel: u8,
35 obniz: Obniz,
36}
37
38impl PwmChannel {
39 pub fn new(channel: u8, obniz: Obniz) -> Self {
40 Self { channel, obniz }
41 }
42
43 pub fn channel_key(&self) -> String {
44 format!("pwm{}", self.channel)
45 }
46
47 pub async fn init(&self, io_pin: u8) -> ObnizResult<()> {
49 if self.channel > 5 {
50 return Err(ObnizError::Generic("PWM channel must be 0-5".to_string()));
51 }
52
53 if io_pin > 11 {
54 return Err(ObnizError::InvalidPin(io_pin));
55 }
56
57 let channel_key = self.channel_key();
58 let request = json!([{&channel_key: {"io": io_pin}}]);
59 let message = Message::from(request.to_string());
60
61 self.obniz
62 .send_message(message)
63 .map_err(|e| ObnizError::Connection(e.to_string()))
64 }
65
66 pub async fn set_frequency(&self, frequency: u32) -> ObnizResult<()> {
68 if frequency == 0 || frequency > 80_000_000 {
69 return Err(ObnizError::Generic(
70 "Frequency must be between 1 and 80,000,000 Hz".to_string(),
71 ));
72 }
73
74 let channel_key = self.channel_key();
75 let request = json!([{&channel_key: {"freq": frequency}}]);
76 let message = Message::from(request.to_string());
77
78 self.obniz
79 .send_message(message)
80 .map_err(|e| ObnizError::Connection(e.to_string()))
81 }
82
83 pub async fn set_pulse_width(&self, pulse_width_ms: f64) -> ObnizResult<()> {
85 if pulse_width_ms < 0.0 {
86 return Err(ObnizError::Generic("Pulse width must be >= 0".to_string()));
87 }
88
89 let channel_key = self.channel_key();
90 let request = json!([{&channel_key: {"pulse": pulse_width_ms}}]);
91 let message = Message::from(request.to_string());
92
93 self.obniz
94 .send_message(message)
95 .map_err(|e| ObnizError::Connection(e.to_string()))
96 }
97
98 pub async fn set_duty_cycle(&self, frequency: u32, duty_percent: f64) -> ObnizResult<()> {
100 if !(0.0..=100.0).contains(&duty_percent) {
101 return Err(ObnizError::Generic(
102 "Duty cycle must be between 0 and 100%".to_string(),
103 ));
104 }
105
106 let period_ms = 1000.0 / frequency as f64;
108 let pulse_width_ms = period_ms * duty_percent / 100.0;
109
110 self.set_frequency(frequency).await?;
111 self.set_pulse_width(pulse_width_ms).await
112 }
113
114 pub async fn configure(&self, config: PwmConfig) -> ObnizResult<()> {
116 self.init(config.io_pin).await?;
117 self.set_frequency(config.frequency).await?;
118 self.set_pulse_width(config.pulse_width_ms).await
119 }
120
121 pub async fn modulate(&self, config: ModulationConfig) -> ObnizResult<()> {
123 if config.symbol_length_ms < 0.05 || config.symbol_length_ms > 1000.0 {
124 return Err(ObnizError::Generic(
125 "Symbol length must be between 0.05 and 1000 ms".to_string(),
126 ));
127 }
128
129 if config.data.is_empty() {
130 return Err(ObnizError::Generic(
131 "Modulation data cannot be empty".to_string(),
132 ));
133 }
134
135 for &value in &config.data {
137 if value > 1 {
138 return Err(ObnizError::Generic(
139 "Modulation data must contain only 0 and 1".to_string(),
140 ));
141 }
142 }
143
144 let channel_key = self.channel_key();
145 let request = json!([{&channel_key: {
146 "modulate": {
147 "type": config.modulation_type,
148 "symbol_length": config.symbol_length_ms,
149 "data": config.data
150 }
151 }}]);
152 let message = Message::from(request.to_string());
153
154 self.obniz
155 .send_message(message)
156 .map_err(|e| ObnizError::Connection(e.to_string()))
157 }
158
159 pub async fn square_wave(&self, io_pin: u8, frequency: u32) -> ObnizResult<()> {
161 let config = PwmConfig {
162 io_pin,
163 frequency,
164 pulse_width_ms: 500.0 / frequency as f64, };
166 self.configure(config).await
167 }
168
169 pub async fn servo(&self, io_pin: u8, angle: f64) -> ObnizResult<()> {
171 if !(0.0..=180.0).contains(&angle) {
172 return Err(ObnizError::Generic(
173 "Servo angle must be between 0 and 180 degrees".to_string(),
174 ));
175 }
176
177 let pulse_width_ms = 1.0 + (angle / 180.0);
179
180 let config = PwmConfig {
181 io_pin,
182 frequency: 50, pulse_width_ms,
184 };
185 self.configure(config).await
186 }
187
188 pub async fn deinit(&self) -> ObnizResult<()> {
190 let channel_key = self.channel_key();
191 let request = json!([{&channel_key: null}]);
192 let message = Message::from(request.to_string());
193
194 self.obniz
195 .send_message(message)
196 .map_err(|e| ObnizError::Connection(e.to_string()))
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct PwmManager {
203 obniz: Obniz,
204}
205
206impl PwmManager {
207 pub fn new(obniz: Obniz) -> Self {
208 Self { obniz }
209 }
210
211 pub fn channel(&self, channel: u8) -> ObnizResult<PwmChannel> {
213 if channel > 5 {
214 return Err(ObnizError::Generic("PWM channel must be 0-5".to_string()));
215 }
216 Ok(PwmChannel::new(channel, self.obniz.clone()))
217 }
218
219 pub async fn configure_channel(&self, channel: u8, config: PwmConfig) -> ObnizResult<()> {
221 self.channel(channel)?.configure(config).await
222 }
223
224 pub async fn set_channel_frequency(&self, channel: u8, frequency: u32) -> ObnizResult<()> {
226 self.channel(channel)?.set_frequency(frequency).await
227 }
228
229 pub async fn set_channel_pulse_width(
231 &self,
232 channel: u8,
233 pulse_width_ms: f64,
234 ) -> ObnizResult<()> {
235 self.channel(channel)?.set_pulse_width(pulse_width_ms).await
236 }
237
238 pub async fn set_channel_duty_cycle(
240 &self,
241 channel: u8,
242 frequency: u32,
243 duty_percent: f64,
244 ) -> ObnizResult<()> {
245 self.channel(channel)?
246 .set_duty_cycle(frequency, duty_percent)
247 .await
248 }
249
250 pub async fn square_wave(&self, channel: u8, io_pin: u8, frequency: u32) -> ObnizResult<()> {
252 self.channel(channel)?.square_wave(io_pin, frequency).await
253 }
254
255 pub async fn servo(&self, channel: u8, io_pin: u8, angle: f64) -> ObnizResult<()> {
257 self.channel(channel)?.servo(io_pin, angle).await
258 }
259
260 pub async fn deinit_channel(&self, channel: u8) -> ObnizResult<()> {
262 self.channel(channel)?.deinit().await
263 }
264
265 pub async fn deinit_all(&self) -> ObnizResult<()> {
267 for channel in 0..=5 {
268 if let Err(e) = self.deinit_channel(channel).await {
269 eprintln!("Failed to deinitialize PWM channel {channel}: {e}");
270 }
271 }
272 Ok(())
273 }
274
275 pub fn duty_cycle_to_pulse_width(frequency: u32, duty_percent: f64) -> f64 {
277 let period_ms = 1000.0 / frequency as f64;
278 period_ms * duty_percent / 100.0
279 }
280
281 pub fn pulse_width_to_duty_cycle(frequency: u32, pulse_width_ms: f64) -> f64 {
283 let period_ms = 1000.0 / frequency as f64;
284 (pulse_width_ms / period_ms * 100.0).clamp(0.0, 100.0)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_pwm_config_creation() {
294 let config = PwmConfig {
295 io_pin: 5,
296 frequency: 1000,
297 pulse_width_ms: 0.5,
298 };
299
300 assert_eq!(config.io_pin, 5);
301 assert_eq!(config.frequency, 1000);
302 assert_eq!(config.pulse_width_ms, 0.5);
303 }
304
305 #[test]
306 fn test_modulation_config_creation() {
307 let config = ModulationConfig {
308 modulation_type: ModulationType::Am,
309 symbol_length_ms: 100.0,
310 data: vec![0, 1, 1, 0],
311 };
312
313 assert_eq!(config.modulation_type, ModulationType::Am);
314 assert_eq!(config.symbol_length_ms, 100.0);
315 assert_eq!(config.data, vec![0, 1, 1, 0]);
316 }
317
318 #[test]
319 fn test_duty_cycle_calculations() {
320 let pulse_width = PwmManager::duty_cycle_to_pulse_width(1000, 50.0);
322 assert_eq!(pulse_width, 0.5); let duty_cycle = PwmManager::pulse_width_to_duty_cycle(1000, 0.25);
326 assert_eq!(duty_cycle, 25.0); }
328
329 #[test]
330 fn test_modulation_type_serialization() {
331 use serde_json;
332
333 let mod_type = ModulationType::Am;
334 let serialized = serde_json::to_string(&mod_type).unwrap();
335 assert_eq!(serialized, "\"am\"");
336 }
337
338 #[test]
339 fn test_channel_key_generation() {
340 assert_eq!(format!("pwm{}", 0), "pwm0");
341 assert_eq!(format!("pwm{}", 5), "pwm5");
342 }
343}