Skip to main content

dy_sv5w/
lib.rs

1//! Library for UART-mode of the DY-SV5W voice module (and compatible modules)
2//!
3//! This is a [no-std] crate which uses a trait to handle I/O. See the two examples on how to implement such an interface to your UART.
4//! Although primarily designed for embedded usage, it uses ASYNC to profit from async-frameworks like Embassy.
5//!
6#![no_std]
7use core::future::Future;
8const CMD_START: u8 = 0xaa;
9
10
11#[derive(PartialEq, Debug, Clone, Copy)]
12pub enum PlayState {
13    Stop,
14    Play,
15    Pause,
16    Unknown
17}
18
19#[derive(PartialEq, Debug, Clone, Copy)]
20pub enum Drive {
21    USB,
22    SD,
23    Flash,
24    NoDevice
25}
26
27#[derive(PartialEq, Debug, Clone, Copy)]
28pub enum EqualizerMode {
29    Normal,
30    Pop,
31    Rock,
32    Jazz,
33    Classic
34}
35
36pub struct DySv5w<T> {
37    serial: T
38}
39
40impl<T> DySv5w<T>
41where T: DySv5wSerialIO
42{
43    pub fn new(serial: T) -> DySv5w<T> {
44        DySv5w {
45            serial
46        }
47    }
48
49    /// Set the volume of at an absolute value
50    /// volume is in the range of 0..31
51    pub async fn set_volume(&mut self, volume: u8) {
52        let mut cmd = [CMD_START, 0x13, 0x01, volume, 0x00];
53        self.send_with_crc(&mut cmd).await;
54    }
55
56    /// Starts playing the currently selected song. Use 'specify_song()' to select which one.
57    pub async fn play(&mut self) {
58        let mut cmd = [CMD_START, 0x02, 0x00, 0];
59        self.send_with_crc(&mut cmd).await;
60    }
61
62    pub async fn pause(&mut self) {
63        let mut cmd = [CMD_START, 0x03, 0x00, 0];
64        self.send_with_crc(&mut cmd).await;
65    }
66
67    pub async fn stop(&mut self) {
68        let mut cmd = [CMD_START, 0x04, 0x00, 0];
69        self.send_with_crc(&mut cmd).await;
70    }
71
72    pub async fn previous(&mut self) {
73        let mut cmd = [CMD_START, 0x05, 0x00, 0];
74        self.send_with_crc(&mut cmd).await;
75    }
76
77    pub async fn volume_inc(&mut self) {
78        let mut cmd = [CMD_START, 0x14, 0x00, 0];
79        self.send_with_crc(&mut cmd).await;
80    }
81
82    pub async fn volume_dec(&mut self) {
83        let mut cmd = [CMD_START, 0x15, 0x00, 0];
84        self.send_with_crc(&mut cmd).await;
85    }
86
87    pub async fn next(&mut self) {
88        let mut cmd = [CMD_START, 0x06, 0x00, 0];
89        self.send_with_crc(&mut cmd).await;
90    }
91
92    pub async fn stop_playing(&mut self) {
93        let mut cmd = [CMD_START, 0x10, 0x00, 0];
94        self.send_with_crc(&mut cmd).await;
95    }
96    
97    pub async fn specify_song(&mut self, song: u16) {
98        let hl = song.to_be_bytes();
99        let mut cmd = [CMD_START, 0x07, 0x02, hl[0], hl[1], 0];
100        self.send_with_crc(&mut cmd).await;
101    }
102
103    /// The effect of this configuration is unclear for now
104    pub async fn set_cycle_times(&mut self, times: u16) {
105        let hl = times.to_be_bytes();
106        let mut cmd = [CMD_START, 0x19, 0x02, hl[0], hl[1], 0];
107        self.send_with_crc(&mut cmd).await;
108    }
109
110    pub async fn set_equalizer_mode(&mut self, mode: EqualizerMode) {
111        let mut cmd = [CMD_START, 0x1a, 0x01, mode.to_u8(), 0];
112        self.send_with_crc(&mut cmd).await;
113    }
114    
115    pub async fn query_play_status(&mut self) -> Option<PlayState> {
116        let mut cmd = [CMD_START, 0x01, 0x00, 0];
117        self.send_with_crc(&mut cmd).await;
118        let mut rcv_buffer = [0u8; 1];
119        if self.receive_answer(0x01, &mut rcv_buffer).await {
120            match rcv_buffer[0] {
121                0 => Some(PlayState::Stop),
122                1 => Some(PlayState::Play),
123                2 => Some(PlayState::Pause),
124                _ => None
125            }
126        } else {
127            None
128        }
129    }
130
131    pub async fn switch_specified_drive(&mut self, drive: Drive) {
132        let mut cmd = [CMD_START, 0x0b, 0x01, drive.to_u8(), 0];
133        self.send_with_crc(&mut cmd).await;
134    }
135
136    pub async fn query_current_play_drive(&mut self) -> Option<Drive> {
137        let mut cmd = [CMD_START, 0x0a, 0x00, 0];
138        self.send_with_crc(&mut cmd).await;
139        let mut rcv_buffer = [0u8; 1];
140        if self.receive_answer(0x0a, &mut rcv_buffer).await {
141            Some(Drive::from_u8(rcv_buffer[0]))
142        } else {
143            None
144        }
145    }
146
147    pub async fn query_current_online_drive(&mut self) -> Option<Drive> {
148        let mut cmd = [CMD_START, 0x09, 0x00, 0];
149        self.send_with_crc(&mut cmd).await;
150        let mut rcv_buffer = [0u8; 1];
151        if self.receive_answer(0x09, &mut rcv_buffer).await {
152            Some(Drive::from_u8(rcv_buffer[0]))
153        } else {
154            None
155        }
156    }
157
158    pub async fn query_number_songs(&mut self) -> Option<u16> {
159        let mut cmd = [CMD_START, 0x0c, 0x00, 0];
160        self.send_with_crc(&mut cmd).await;
161        let mut rcv_buffer = [0u8; 2];
162        if self.receive_answer(0x0c, &mut rcv_buffer).await {
163            Some(get_word(&rcv_buffer))
164        } else {
165            None
166        }
167    }
168
169    pub async fn query_current_song(&mut self) -> Option<u16> {
170        let mut cmd = [CMD_START, 0x0d, 0x00, 0];
171        self.send_with_crc(&mut cmd).await;
172        let mut rcv_buffer = [0u8; 2];
173        if self.receive_answer(0x0d, &mut rcv_buffer).await {
174            Some(get_word(&rcv_buffer))
175        } else {
176            None
177        }
178    }
179
180
181    async fn send_with_crc(&mut self, cmd: &mut [u8]) {
182        fill_in_crc(cmd);
183        self.serial.send_data(cmd).await
184    }
185    async fn receive_answer(&mut self, cmd: u8, buffer: &mut [u8]) -> bool {
186        let Some(start) = self.serial.read_byte().await else {
187            return false;
188        };
189        if start != CMD_START {
190            return false;
191        }
192        let Some(cmd_in) = self.serial.read_byte().await else {
193            return false;
194        };
195        if cmd_in != cmd {
196            return false;
197        }
198        let Some(mut len) = self.serial.read_byte().await else {
199            return false;
200        };
201        if len as usize != buffer.len() {
202            return false;
203        }
204        let mut pos = 0;
205        while len > 0 {
206            let Some(data) = self.serial.read_byte().await else {
207                return false;
208            };
209            buffer[pos] = data;
210            len -= 1;
211            pos += 1;
212        }
213        let _ = self.serial.read_byte().await;  // ignore CRC for now
214        true
215    }
216}
217
218fn fill_in_crc(bytes: &mut [u8]) {
219    if bytes.is_empty() {
220        return;
221    }
222    let mut crc: u16 = 0;
223    for b in bytes.iter() {
224        crc += *b as u16;
225    }
226    bytes[bytes.len()-1] = (crc&0xff) as u8;
227}
228
229fn get_word(bytes: &[u8; 2]) -> u16 {
230    let mut word: u16 = (bytes[0] as u16) << 8;
231    word |= bytes[1] as u16;
232    word
233}
234
235/// This trait needs to be implemented as an interface to the actual hardware interfacing the sound module
236/// The UART needs to be set up with 9600,8N1
237pub trait DySv5wSerialIO {
238    /// Sends out data buffer. Expects output to be flushed.
239    fn send_data(&mut self, data: &[u8]) -> impl Future<Output = ()>;
240    /// Reads one byte from serial port. Timeout should be around 1 second.
241    /// Read-error or timeout is indicated by returning NONE
242    fn read_byte(&mut self) -> impl Future<Output = Option<u8>>;
243}
244
245impl Drive {
246    fn from_u8(value: u8) -> Drive {
247        match value {
248            0 => Drive::USB,
249            1 => Drive::SD,
250            2 => Drive::Flash,
251            _ => Drive::NoDevice,
252        }
253    }
254
255    fn to_u8(self) -> u8 {
256        match self {
257            Drive::USB => { 0 }
258            Drive::SD => { 1 }
259            Drive::Flash => { 2 }
260            Drive::NoDevice => { 0xff }
261        }
262    }
263}
264
265impl EqualizerMode {
266    fn to_u8(self) -> u8 {
267        match self {
268            EqualizerMode::Normal => { 0 }
269            EqualizerMode::Pop => { 1 }
270            EqualizerMode::Rock => { 2 }
271            EqualizerMode::Jazz => { 3 }
272            EqualizerMode::Classic => { 4 }
273        }
274    }
275}