dfplayer_async/lib.rs
1//! A no_std async library for interfacing with DFPlayer Mini MP3 modules
2//!
3//! This crate provides an async interface to control DFPlayer Mini MP3 modules
4//! using embedded-hal-async compatible serial interfaces. It handles the binary
5//! protocol, error checking, and timeout management required when communicating
6//! with these devices.
7//!
8//! ## Features
9//!
10//! - Async/await API for embedded systems
11//! - Full command support for DFPlayer Mini and compatible modules
12//! - Proper error handling and timeout management
13//! - no_std compatible
14//! - Robust initialization sequence with fallback mechanisms
15//! - Efficient non-blocking I/O patterns for embedded environments
16//!
17//! ## Example
18//!
19//! ```rust,no_run
20//! use dfplayer_async::{DfPlayer, PlayBackSource, TimeSource};
21//! use embassy_time::{Duration, Instant, Delay};
22//!
23//! // Define a time source for the DFPlayer
24//! struct MyTimeSource;
25//! impl TimeSource for MyTimeSource {
26//! type Instant = Instant;
27//! fn now(&self) -> Self::Instant { Instant::now() }
28//! fn is_elapsed(&self, since: Self::Instant, timeout_ms: u64) -> bool {
29//! Instant::now().duration_since(since) >= Duration::from_millis(timeout_ms)
30//! }
31//! }
32//!
33//! // In your async function:
34//! async fn example(mut uart: impl embedded_io_async::Read + embedded_io_async::Write + embedded_io_async::ReadReady) {
35//! let mut dfplayer = DfPlayer::new(
36//! &mut uart, // UART port (9600 baud, 8N1)
37//! false, // feedback_enable
38//! 1000, // timeout_ms
39//! MyTimeSource, // time source
40//! Delay, // delay provider
41//! None, // reset_duration_override
42//! ).await.expect("Failed to initialize DFPlayer");
43//!
44//! // Play first track
45//! dfplayer.play(1).await.expect("Failed to play track");
46//! }
47//! ```
48//!
49//! This crate optionally supports logging via the defmt framework.
50//! Enable the "defmt" feature to activate logging.
51
52#![no_std]
53
54use embedded_hal_async::delay::DelayNs;
55use embedded_io_async::{Read, ReadReady, Write};
56
57#[cfg(feature = "defmt")]
58use defmt::{Debug2Format, info};
59
60// Protocol constants
61const START_BYTE: u8 = 0x7E;
62const END_BYTE: u8 = 0xEF;
63const VERSION: u8 = 0xFF;
64const MSG_LEN: u8 = 0x06;
65const DATA_FRAME_SIZE: usize = 10;
66
67// Message byte indices
68const INDEX_VERSION: usize = 1;
69const INDEX_CMD: usize = 3;
70const INDEX_FEEDBACK_ENABLE: usize = 4;
71const INDEX_PARAM_H: usize = 5;
72const INDEX_PARAM_L: usize = 6;
73const INDEX_CHECKSUM_H: usize = 7;
74const INDEX_CHECKSUM_L: usize = 8;
75
76/// Minimal time provider trait for timeout tracking. Implement this for your platform.
77pub trait TimeSource {
78 /// Monotonic time point type
79 type Instant: Copy + Clone + PartialEq + PartialOrd;
80
81 /// Get the current time
82 fn now(&self) -> Self::Instant;
83
84 /// Check if a timeout has occurred
85 fn is_elapsed(&self, since: Self::Instant, timeout_ms: u64) -> bool;
86}
87
88/// Represents available media sources on the DFPlayer
89#[derive(Debug, Clone, Copy)]
90#[repr(u8)]
91#[cfg_attr(feature = "defmt", derive(defmt::Format))]
92pub enum Source {
93 /// Internal USB flash storage
94 USBFlash = 0b001,
95 /// SD card inserted in the module
96 SDCard = 0b010,
97 /// External USB device connected to the module
98 USBHost = 0b100,
99}
100
101impl TryFrom<u8> for Source {
102 type Error = ();
103 fn try_from(value: u8) -> Result<Self, Self::Error> {
104 match value {
105 0b001 => Ok(Source::USBFlash),
106 0b010 => Ok(Source::SDCard),
107 0b100 => Ok(Source::USBHost),
108 _ => Err(()),
109 }
110 }
111}
112
113/// Error codes reported by the DFPlayer module
114#[derive(Debug, Clone, Copy)]
115#[repr(u8)]
116#[cfg_attr(feature = "defmt", derive(defmt::Format))]
117pub enum ModuleError {
118 /// Module is currently busy
119 Busy = 1,
120 /// Module is in sleep mode
121 Sleeping = 2,
122 /// Serial receive error occurred
123 SerialRxError = 3,
124 /// Checksum validation failed
125 Checksum = 4,
126 /// Requested track is out of valid range
127 TrackNotInScope = 5,
128 /// Track was not found on the media
129 TrackNotFound = 6,
130 /// Error inserting file/track
131 InsertionError = 7,
132 /// Module entering sleep mode
133 EnterSleep = 8,
134}
135
136impl TryFrom<u8> for ModuleError {
137 type Error = ();
138 fn try_from(value: u8) -> Result<Self, Self::Error> {
139 match value {
140 1 => Ok(ModuleError::Busy),
141 2 => Ok(ModuleError::Sleeping),
142 3 => Ok(ModuleError::SerialRxError),
143 4 => Ok(ModuleError::Checksum),
144 5 => Ok(ModuleError::TrackNotInScope),
145 6 => Ok(ModuleError::TrackNotFound),
146 7 => Ok(ModuleError::InsertionError),
147 8 => Ok(ModuleError::EnterSleep),
148 _ => Err(()),
149 }
150 }
151}
152
153/// Errors that can occur when operating the DFPlayer
154#[derive(Debug, Clone, Copy)]
155#[cfg_attr(feature = "defmt", derive(defmt::Format))]
156pub enum Error<SerialError> {
157 /// Initialization failed
158 Init,
159 /// Serial port communication error
160 SerialPort(SerialError),
161 /// Delay implementation failed
162 DelayError,
163 /// Command was not acknowledged when feedback was enabled
164 FailedAck,
165 /// Connection to the module was lost
166 Connection,
167 /// Error reported by the module itself
168 ModuleError(ModuleError),
169 /// Unknown error occurred
170 Unknown,
171 /// Received message was corrupted or incomplete
172 BrokenMessage,
173 /// Operation timed out
174 UserTimeout,
175 /// Command parameter was invalid
176 BadParameter,
177}
178
179/// Data structure representing a message to/from the DFPlayer
180#[derive(PartialEq, Debug, Clone, Copy)]
181#[cfg_attr(feature = "defmt", derive(defmt::Format))]
182pub struct MessageData {
183 command: Command,
184 param_h: u8,
185 param_l: u8,
186}
187
188impl MessageData {
189 /// Create a new message with the specified command and parameters
190 pub const fn new(command: Command, param_h: u8, param_l: u8) -> Self {
191 Self {
192 command,
193 param_h,
194 param_l,
195 }
196 }
197}
198
199/// Message used for acknowledging commands
200const ACK_MESSAGE_DATA: MessageData =
201 MessageData::new(Command::NotifyReply, 0, 0);
202
203/// Commands supported by the DFPlayer module
204#[repr(u8)]
205#[derive(PartialEq, Debug, Clone, Copy)]
206#[cfg_attr(feature = "defmt", derive(defmt::Format))]
207pub enum Command {
208 /// Play next file
209 Next = 0x01,
210 /// Play previous file
211 Previous = 0x02,
212 /// Specify Track number to play (1-2999)
213 PlayTrack = 0x03,
214 /// Increase Volume
215 VolumeUp = 0x04,
216 /// Decrease Volume
217 VolumeDown = 0x05,
218 /// Set specific volume value, 0-30
219 SetVolume = 0x06,
220 /// Select equalizer
221 SetEQ = 0x07,
222 /// Select track to play in loop
223 PlayLoopTrack = 0x08,
224 /// Select the Playback source (USDB/SD)
225 SetPlaybackSource = 0x09,
226 /// Enter Sleep/StandBy Mode
227 EnterSleepMode = 0x0A,
228 /// Normal mode per DFRobot, but it's reported it does nothing
229 EnterNormalMode = 0x0B,
230 /// Reset the device
231 Reset = 0x0C,
232 /// Start Playback
233 Play = 0x0D,
234 /// Pause Current Playback
235 Pause = 0x0E,
236 /// Specify Track to play in a folder, 99 folders 255 tracks each max
237 PlayTrackInFolder = 0x0F,
238 /// Configure the audio amplifier gain settings, MSB enables amp, LS sets
239 /// gain 0-31
240 ConfigAudioAmp = 0x10,
241 /// Play all tracks in a loop
242 PlayLoopAll = 0x11,
243 /// Play track number in MP3 folder, max 65536 tracks, 3000 recommended max
244 PlayTrackInMp3Folder = 0x12,
245 /// Play track number in ADVERT folder, max 3000 tracks
246 StartAdvertisement = 0x13,
247 /// Play track of large folder, 1-3000 valid names
248 PlayTrackLargeFolder = 0x14,
249 /// Stop playing ADVERT track if one is active
250 StopAdvertisement = 0x15,
251 /// Stop all playback including advertisement
252 Stop = 0x16,
253 /// Play tracks from a folder on repeat, max 99 folders, 255 files each
254 PlayLoopFolder = 0x17,
255 /// Play random tracks from all media available in the current source
256 PlayRandom = 0x18,
257 /// If a track is playing, control loop playback enable (0x1 enable, 0x0 disable)
258 LoopCurrentTrack = 0x19,
259 /// Control whether the DAC is powered on or off
260 SetDAC = 0x1a,
261 /// Only sent by module when media is connected
262 NotifyPushMedia = 0x3A,
263 /// Only sent by module when media is removed
264 NotifyPullOutMedia = 0x3B,
265 /// Only sent by module when track in USB Flash finished playing
266 NotifyFinishTrackUSBFlash = 0x3C,
267 /// Only sent by module when track in SD card finished playing
268 NotifyFinishTrackSD = 0x3D,
269 /// Only sent by module when track in USB Host link stopped playing
270 NotifyFinishTrackUSBHost = 0x3E,
271 /// List the available sources. For the DFPlayer Mini, it's essentially
272 /// only the SD card
273 QueryAvailableSources = 0x3F,
274 /// Only sent by module when an error occurs
275 NotifyError = 0x40,
276 /// Sent as ACK response when feedback is enabled
277 NotifyReply = 0x41,
278 /// Returns status of module
279 QueryStatus = 0x42,
280 /// Returns current volume setting
281 QueryVolume = 0x43,
282 /// Returns current EQ setting
283 QueryEQ = 0x44,
284 /// Returns current playback mode
285 ReservedQueryPlaybackMode = 0x45,
286 /// Returns software version
287 ReservedQuerySwVersion = 0x46,
288 /// Returns number of tracks on USB storage
289 QueryTrackCntUSB = 0x47,
290 /// Returns number of tracks on SD card
291 QueryTrackCntSD = 0x48,
292 /// Returns number of tracks on PC
293 ReservedQueryTrackCntPC = 0x49,
294 /// Query the keep-on setting
295 ReservedQueryKeepOn = 0x4A,
296 /// Returns current track number on USB flash
297 QueryCurrentTrackUSBFlash = 0x4B,
298 /// Returns current track number on SD card
299 QueryCurrentTrackSD = 0x4C,
300 /// Returns current track number on USB host
301 QueryCurrentTrackUSBHost = 0x4D,
302 /// Returns number of tracks in current folder
303 QueryFolderTrackCnt = 0x4E,
304 /// Returns number of folders available
305 QueryFolderCnt = 0x4F,
306}
307
308impl TryFrom<u8> for Command {
309 type Error = ();
310 fn try_from(value: u8) -> Result<Self, Self::Error> {
311 match value {
312 0x01 => Ok(Command::Next),
313 0x02 => Ok(Command::Previous),
314 0x03 => Ok(Command::PlayTrack),
315 0x04 => Ok(Command::VolumeUp),
316 0x05 => Ok(Command::VolumeDown),
317 0x06 => Ok(Command::SetVolume),
318 0x07 => Ok(Command::SetEQ),
319 0x08 => Ok(Command::PlayLoopTrack),
320 0x09 => Ok(Command::SetPlaybackSource),
321 0x0A => Ok(Command::EnterSleepMode),
322 0x0B => Ok(Command::EnterNormalMode),
323 0x0C => Ok(Command::Reset),
324 0x0D => Ok(Command::Play),
325 0x0E => Ok(Command::Pause),
326 0x0F => Ok(Command::PlayTrackInFolder),
327 0x10 => Ok(Command::ConfigAudioAmp),
328 0x11 => Ok(Command::PlayLoopAll),
329 0x12 => Ok(Command::PlayTrackInMp3Folder),
330 0x13 => Ok(Command::StartAdvertisement),
331 0x14 => Ok(Command::PlayTrackLargeFolder),
332 0x15 => Ok(Command::StopAdvertisement),
333 0x16 => Ok(Command::Stop),
334 0x17 => Ok(Command::PlayLoopFolder),
335 0x18 => Ok(Command::PlayRandom),
336 0x19 => Ok(Command::LoopCurrentTrack),
337 0x1A => Ok(Command::SetDAC),
338 0x3A => Ok(Command::NotifyPushMedia),
339 0x3B => Ok(Command::NotifyPullOutMedia),
340 0x3C => Ok(Command::NotifyFinishTrackUSBFlash),
341 0x3D => Ok(Command::NotifyFinishTrackSD),
342 0x3E => Ok(Command::NotifyFinishTrackUSBHost),
343 0x3F => Ok(Command::QueryAvailableSources),
344 0x40 => Ok(Command::NotifyError),
345 0x41 => Ok(Command::NotifyReply),
346 0x42 => Ok(Command::QueryStatus),
347 0x43 => Ok(Command::QueryVolume),
348 0x44 => Ok(Command::QueryEQ),
349 0x45 => Ok(Command::ReservedQueryPlaybackMode),
350 0x46 => Ok(Command::ReservedQuerySwVersion),
351 0x47 => Ok(Command::QueryTrackCntUSB),
352 0x48 => Ok(Command::QueryTrackCntSD),
353 0x49 => Ok(Command::ReservedQueryTrackCntPC),
354 0x4A => Ok(Command::ReservedQueryKeepOn),
355 0x4B => Ok(Command::QueryCurrentTrackUSBFlash),
356 0x4C => Ok(Command::QueryCurrentTrackSD),
357 0x4D => Ok(Command::QueryCurrentTrackUSBHost),
358 0x4E => Ok(Command::QueryFolderTrackCnt),
359 0x4F => Ok(Command::QueryFolderCnt),
360 _ => Err(()),
361 }
362 }
363}
364
365/// Equalizer settings available on the DFPlayer
366#[repr(u8)]
367#[derive(Clone, Copy, Debug)]
368#[cfg_attr(feature = "defmt", derive(defmt::Format))]
369pub enum Equalizer {
370 /// Normal (flat) equalizer setting
371 Normal = 0x0,
372 /// Pop music equalizer preset
373 Pop = 0x1,
374 /// Rock music equalizer preset
375 Rock = 0x2,
376 /// Jazz music equalizer preset
377 Jazz = 0x3,
378 /// Classical music equalizer preset
379 Classic = 0x4,
380 /// Bass boost equalizer preset
381 Bass = 0x5,
382}
383
384/// Playback modes supported by the DFPlayer
385#[repr(u8)]
386#[derive(Clone, Copy, Debug)]
387#[cfg_attr(feature = "defmt", derive(defmt::Format))]
388pub enum PlayBackMode {
389 /// Repeat all tracks
390 Repeat = 0x0,
391 /// Repeat tracks in current folder
392 FolderRepeat = 0x1,
393 /// Repeat single track
394 SingleRepeat = 0x2,
395 /// Play tracks in random order
396 Random = 0x3,
397}
398
399/// Media sources supported by the DFPlayer
400#[repr(u8)]
401#[derive(Clone, Copy, Debug)]
402#[cfg_attr(feature = "defmt", derive(defmt::Format))]
403pub enum PlayBackSource {
404 /// USB storage device
405 USB = 0x0,
406 /// SD card
407 SDCard = 0x1,
408 /// Auxiliary input
409 Aux = 0x2,
410 /// Sleep mode (no source)
411 Sleep = 0x3,
412 /// Flash memory
413 Flash = 0x4,
414}
415
416/// Calculate the checksum for a DFPlayer message
417///
418/// The checksum is calculated by summing all bytes from version to parameters
419/// and then taking the two's complement of the sum.
420pub fn checksum(buffer: &[u8]) -> u16 {
421 let mut checksum = 0;
422 for &b in buffer {
423 checksum += u16::from(b);
424 }
425 if buffer[2] == 0x0 {
426 checksum += 2
427 };
428 0u16.wrapping_sub(checksum)
429}
430
431/// Main driver for interfacing with DFPlayer Mini modules
432pub struct DfPlayer<'a, S, T, D>
433where
434 S: Read + Write + ReadReady,
435 T: TimeSource,
436 D: DelayNs,
437{
438 port: &'a mut S,
439 feedback_enable: bool,
440 last_command: MessageData,
441 last_response: MessageData,
442 last_cmd_acknowledged: bool,
443 timeout_ms: u64,
444 time_source: T,
445 delay: D,
446}
447
448/// Structure for interacting with the device
449impl<'a, S, T, D> DfPlayer<'a, S, T, D>
450where
451 S: Read + Write + ReadReady,
452 T: TimeSource,
453 D: DelayNs,
454{
455 /// Create a new DFPlayer interface
456 ///
457 /// This initializes the driver and performs a robust startup sequence for the DFPlayer module.
458 /// The serial port must be configured with 9600 baud, 8N1 format before calling this function.
459 ///
460 /// The initialization sequence:
461 /// 1. Clears any pending data in the receive buffer
462 /// 2. Sends a reset command and waits for the device to restart
463 /// 3. Configures SD card as the default media source
464 /// 4. Sets the volume to a moderate level (15 out of 30)
465 ///
466 /// The function will attempt to continue even if certain initialization steps fail,
467 /// making it more resilient to communication issues common with these modules.
468 ///
469 /// # Arguments
470 /// * `port` - Serial port connected to the DFPlayer module
471 /// * `feedback_enable` - Whether to enable command acknowledgement (set to false if you're having reliability issues)
472 /// * `timeout_ms` - Timeout for operations in milliseconds
473 /// * `time_source` - Source of time for timeout tracking
474 /// * `delay` - Delay provider for timing operations
475 /// * `reset_duration_override` - Optional override for reset delay duration (ms)
476 pub async fn new(
477 port: &'a mut S,
478 feedback_enable: bool,
479 timeout_ms: u64,
480 time_source: T,
481 delay: D,
482 reset_duration_override: Option<u64>,
483 ) -> Result<Self, Error<S::Error>> {
484 #[cfg(feature = "defmt")]
485 info!("=== DfPlayer::new starting ===");
486 let mut player = Self {
487 port,
488 feedback_enable,
489 last_command: MessageData::new(Command::EnterNormalMode, 0, 0),
490 last_response: MessageData::new(Command::EnterNormalMode, 0, 0),
491 last_cmd_acknowledged: false,
492 timeout_ms,
493 time_source,
494 delay,
495 };
496
497 // Clear any pending data in the receive buffer first
498 #[cfg(feature = "defmt")]
499 info!("Clearing initial receive buffer");
500 let _ = player.clear_receive_buffer().await;
501 #[cfg(feature = "defmt")]
502 info!("Initial buffer clear completed");
503
504 // Send reset command with longer timeout for initialization
505 #[cfg(feature = "defmt")]
506 info!("About to send reset command");
507
508 // Store original timeout and use a longer one for reset
509 let original_timeout = player.timeout_ms;
510 player.timeout_ms = 2000; // Longer timeout for reset
511
512 // Send the reset command using the special init version that won't hang
513 #[cfg(feature = "defmt")]
514 info!("Calling send_command_init for reset");
515 let _reset_result = player
516 .send_command_init(MessageData::new(Command::Reset, 0, 0))
517 .await;
518 #[cfg(feature = "defmt")]
519 info!("Reset command send_command_init completed");
520
521 // Wait for device reset regardless of command result
522 let wait_ms = reset_duration_override.unwrap_or(1500);
523 #[cfg(feature = "defmt")]
524 info!("Waiting {}ms for device reset", wait_ms);
525 player.delay.delay_ms(wait_ms as u32).await;
526 #[cfg(feature = "defmt")]
527 info!("Reset delay completed");
528
529 // Clear any data that might have arrived during reset
530 #[cfg(feature = "defmt")]
531 info!("Clearing buffer after reset");
532 let _ = player.clear_receive_buffer().await;
533 #[cfg(feature = "defmt")]
534 info!("Post-reset buffer clear completed");
535
536 // Restore original timeout
537 player.timeout_ms = original_timeout;
538
539 // Continue even if reset command had issues
540 if let Err(_e) = _reset_result {
541 #[cfg(feature = "defmt")]
542 info!("Reset error: {:?} - continuing anyway", Debug2Format(&_e));
543 }
544
545 // Configure SD card as the default media source
546 #[cfg(feature = "defmt")]
547 info!("About to set playback source to SD card");
548
549 // Use the special init command here too
550 #[cfg(feature = "defmt")]
551 info!("Calling send_command_init for playback source");
552 let _source_result = player
553 .send_command_init(MessageData::new(
554 Command::SetPlaybackSource,
555 0,
556 PlayBackSource::SDCard as u8,
557 ))
558 .await;
559 #[cfg(feature = "defmt")]
560 info!("Playback source command completed");
561
562 if let Err(_e) = _source_result {
563 #[cfg(feature = "defmt")]
564 info!(
565 "Source select warning: {:?} - continuing anyway",
566 Debug2Format(&_e)
567 );
568 }
569
570 // Add a delay after source selection
571 #[cfg(feature = "defmt")]
572 info!("Adding 200ms delay after source selection");
573 player.delay.delay_ms(200).await;
574 #[cfg(feature = "defmt")]
575 info!("Source selection delay completed");
576
577 // Set initial volume to a moderate level
578 #[cfg(feature = "defmt")]
579 info!("About to set initial volume");
580
581 // Use the special init command here too
582 #[cfg(feature = "defmt")]
583 info!("Calling send_command_init for volume");
584 let _vol_result = player
585 .send_command_init(MessageData::new(Command::SetVolume, 0, 15))
586 .await;
587 #[cfg(feature = "defmt")]
588 info!("Volume command completed");
589
590 if let Err(_e) = _vol_result {
591 #[cfg(feature = "defmt")]
592 info!(
593 "Volume set warning: {:?} - continuing anyway",
594 Debug2Format(&_e)
595 );
596 }
597
598 #[cfg(feature = "defmt")]
599 info!("=== DFPlayer initialization complete - SUCCESS! ===");
600
601 Ok(player)
602 }
603
604 /// Read and process a message from the DFPlayer module
605 ///
606 /// This function handles the binary protocol parsing, message validation,
607 /// and stores the last response. It uses a robust state machine to assemble
608 /// complete messages from potentially fragmented reads, with proper timeout
609 /// handling and error recovery.
610 ///
611 /// Special handling is provided for reset commands and 8-byte response formats
612 /// that sometimes occur in feedback mode.
613 ///
614 /// Returns `Ok(())` if a valid message was received and processed, or an error.
615 /// If a module error response was received, returns that specific error.
616 pub async fn read_last_message(&mut self) -> Result<(), Error<S::Error>> {
617 let timeout_start = self.time_source.now();
618
619 // State tracking for message assembly
620 let mut current_index = 0;
621 let mut message = [0u8; DATA_FRAME_SIZE];
622 let mut receive_buffer = [0u8; 32];
623
624 // Continue trying to read until timeout
625 while !self.time_source.is_elapsed(timeout_start, self.timeout_ms) {
626 // Try to read data with graceful fallback if read_ready isn't reliable
627 let bytes_read = match self.port.read_ready() {
628 Ok(true) => match self.port.read(&mut receive_buffer).await {
629 Ok(n) => n,
630 Err(_e) => {
631 #[cfg(feature = "defmt")]
632 info!(
633 "Read error, will retry: {:?}",
634 Debug2Format(&_e)
635 );
636 self.delay.delay_ms(10).await;
637 continue;
638 }
639 },
640 // If read_ready says not ready or errors, we'll still try a read
641 // This helps with UART implementations where read_ready isn't reliable
642 _ => {
643 match self.port.read(&mut receive_buffer).await {
644 Ok(n) => {
645 if n == 0 {
646 // No data available, wait briefly and retry
647 self.delay.delay_ms(10).await;
648 continue;
649 }
650 n
651 }
652 Err(_e) => {
653 #[cfg(feature = "defmt")]
654 info!("Read error: {:?}", Debug2Format(&_e));
655 self.delay.delay_ms(10).await;
656 continue;
657 }
658 }
659 }
660 };
661
662 #[cfg(feature = "defmt")]
663 if bytes_read > 0 {
664 info!(
665 "Read {} bytes: {:?}",
666 bytes_read,
667 &receive_buffer[..bytes_read]
668 );
669 }
670
671 // Special pattern for feedback mode, second responses: 8-byte response format
672 // The DFPlayer sometimes sends truncated 8-byte messages instead of the standard
673 // 10-byte format, particularly for second responses when feedback is enabled.
674 // This appears to be an undocumented protocol quirk of the module.
675 if bytes_read == 8 && receive_buffer[0] == 0x06 {
676 // This looks like a truncated response with the command at index 1
677 let cmd_byte = receive_buffer[1];
678
679 // Try to convert command byte
680 if let Ok(cmd) = Command::try_from(cmd_byte) {
681 self.last_response.command = cmd;
682 self.last_response.param_h = receive_buffer[3];
683 self.last_response.param_l = receive_buffer[4];
684
685 #[cfg(feature = "defmt")]
686 info!(
687 "Parsed 8-byte response: cmd={:?}, params={},{}",
688 cmd,
689 self.last_response.param_h,
690 self.last_response.param_l
691 );
692
693 return Ok(());
694 }
695 }
696
697 // Process each byte through our state machine
698 for i in 0..bytes_read {
699 let byte = receive_buffer[i];
700
701 match current_index {
702 0 => {
703 // State 0: Looking for start byte (0x7E)
704 if byte == START_BYTE {
705 message[current_index] = byte;
706 current_index = 1;
707 }
708 }
709 9 => {
710 // State 9: We have 9 bytes and are looking for the end byte (0xEF)
711 message[current_index] = byte;
712
713 if byte == END_BYTE {
714 // Complete message received, validate and process
715 #[cfg(feature = "defmt")]
716 info!("Complete message: {:?}", message);
717
718 // Validate checksum
719 let read_checksum = ((message[7] as u16) << 8)
720 | (message[8] as u16);
721 let calc_checksum = checksum(&message[1..7]);
722
723 if read_checksum == calc_checksum {
724 // Valid message - extract command and parameters
725 if let Ok(cmd) = Command::try_from(message[3]) {
726 self.last_response.command = cmd;
727 self.last_response.param_h = message[5];
728 self.last_response.param_l = message[6];
729
730 // Check if this is an ACK message
731 if self.last_response == ACK_MESSAGE_DATA {
732 self.last_cmd_acknowledged = true;
733 }
734
735 // Check if this is an error response
736 if self.last_response.command
737 == Command::NotifyError
738 {
739 if let Ok(err) = ModuleError::try_from(
740 self.last_response.param_l,
741 ) {
742 return Err(Error::ModuleError(
743 err,
744 ));
745 } else {
746 return Err(Error::Unknown);
747 }
748 }
749
750 // Valid message processed successfully
751 return Ok(());
752 }
753 } else {
754 #[cfg(feature = "defmt")]
755 info!(
756 "Checksum mismatch: expected {:04X}, got {:04X}",
757 calc_checksum, read_checksum
758 );
759 }
760 }
761
762 // Reset parser state whether valid or not
763 current_index = 0;
764 }
765 _ => {
766 // States 1-8: Collecting the message body
767 message[current_index] = byte;
768 current_index += 1;
769 }
770 }
771 }
772 }
773
774 // If we reached here, we timed out with no valid response
775 #[cfg(feature = "defmt")]
776 info!("Timeout waiting for response");
777
778 // Special case for reset command - just return success
779 if self.last_command.command == Command::Reset {
780 #[cfg(feature = "defmt")]
781 info!("Ignoring timeout for reset command");
782 return Ok(());
783 }
784
785 // Timeout error for other commands
786 Err(Error::UserTimeout)
787 }
788
789 /// Special version of send_command that won't fail if responses aren't received
790 ///
791 /// Used during initialization to improve reliability when the module is first starting up.
792 /// Unlike the regular send_command, this method:
793 /// - Uses shorter timeouts
794 /// - Continues even if responses aren't received
795 /// - Employs simplified error handling
796 ///
797 /// # Arguments
798 /// * `command_data` - The command and parameters to send
799 async fn send_command_init(
800 &mut self,
801 command_data: MessageData,
802 ) -> Result<(), Error<S::Error>> {
803 // Format the command message according to protocol
804 let mut out_buffer = [
805 START_BYTE, VERSION, MSG_LEN, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
806 END_BYTE,
807 ];
808
809 // Set feedback flag if enabled
810 if self.feedback_enable {
811 out_buffer[INDEX_FEEDBACK_ENABLE] = 0x1;
812 }
813
814 // Set command and parameters
815 out_buffer[INDEX_CMD] = command_data.command as u8;
816 out_buffer[INDEX_PARAM_H] = command_data.param_h;
817 out_buffer[INDEX_PARAM_L] = command_data.param_l;
818
819 // Calculate and set checksum
820 let checksum = checksum(&out_buffer[INDEX_VERSION..INDEX_CHECKSUM_H]);
821 out_buffer[INDEX_CHECKSUM_H] = (checksum >> 8) as u8;
822 out_buffer[INDEX_CHECKSUM_L] = checksum as u8;
823
824 // Log the message being sent
825 #[cfg(feature = "defmt")]
826 info!("tx {}", out_buffer);
827
828 // Send the message to the device
829 self.port
830 .write_all(&out_buffer)
831 .await
832 .map_err(Error::SerialPort)?;
833
834 // Store the command for reference
835 self.last_command = command_data;
836 self.last_cmd_acknowledged = false;
837
838 // If feedback is disabled, don't even try to read a response during initialization
839 if !self.feedback_enable {
840 #[cfg(feature = "defmt")]
841 info!(
842 "Skipping response wait during initialization (feedback disabled)"
843 );
844
845 // Still need a small delay to let the command be processed
846 self.delay.delay_ms(50).await;
847 return Ok(());
848 }
849
850 // For feedback mode, use a short timeout for reading during initialization
851 let original_timeout = self.timeout_ms;
852 self.timeout_ms = 200; // Short timeout for init
853
854 // Try to read a response but don't fail if we timeout
855 let result = self.read_last_message().await;
856
857 // Restore original timeout
858 self.timeout_ms = original_timeout;
859
860 // During initialization, continue even if we get a timeout
861 match result {
862 Ok(_) => {
863 #[cfg(feature = "defmt")]
864 info!("Initialization command received response");
865 }
866 Err(Error::UserTimeout) => {
867 #[cfg(feature = "defmt")]
868 info!("Initialization command timed out (continuing anyway)");
869 }
870 Err(_e) => {
871 #[cfg(feature = "defmt")]
872 info!("Initialization command error: {:?}", Debug2Format(&_e));
873 }
874 }
875
876 Ok(())
877 }
878
879 /// Send a command to the DFPlayer module
880 ///
881 /// This constructs a properly formatted message, sends it to the device,
882 /// and then waits for a response or acknowledgement if feedback is enabled.
883 /// For query commands in non-feedback mode, attempts to read and process responses
884 /// with multiple retries if needed.
885 ///
886 /// The method calculates the appropriate checksum and handles all aspects of
887 /// the binary communication protocol.
888 ///
889 /// # Arguments
890 /// * `command_data` - The command and parameters to send
891 pub async fn send_command(
892 &mut self,
893 command_data: MessageData,
894 ) -> Result<(), Error<S::Error>> {
895 // Format the command message according to protocol
896 let mut out_buffer = [
897 START_BYTE, VERSION, MSG_LEN, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
898 END_BYTE,
899 ];
900
901 // Set feedback flag if enabled
902 if self.feedback_enable {
903 out_buffer[INDEX_FEEDBACK_ENABLE] = 0x1;
904 }
905
906 // Set command and parameters
907 out_buffer[INDEX_CMD] = command_data.command as u8;
908 out_buffer[INDEX_PARAM_H] = command_data.param_h;
909 out_buffer[INDEX_PARAM_L] = command_data.param_l;
910
911 // Calculate and set checksum
912 let checksum = checksum(&out_buffer[INDEX_VERSION..INDEX_CHECKSUM_H]);
913 out_buffer[INDEX_CHECKSUM_H] = (checksum >> 8) as u8;
914 out_buffer[INDEX_CHECKSUM_L] = checksum as u8;
915
916 // Log the message being sent
917 #[cfg(feature = "defmt")]
918 info!("tx {}", out_buffer);
919
920 // Send the message to the device
921 self.port
922 .write_all(&out_buffer)
923 .await
924 .map_err(Error::SerialPort)?;
925
926 // Store the command for reference
927 self.last_command = command_data;
928 self.last_cmd_acknowledged = false;
929
930 // Determine if this is a query command
931 let is_query_command = matches!(
932 command_data.command,
933 Command::QueryTrackCntSD
934 | Command::QueryVolume
935 | Command::QueryEQ
936 | Command::QueryAvailableSources
937 | Command::QueryStatus
938 | Command::QueryTrackCntUSB
939 | Command::QueryCurrentTrackUSBFlash
940 | Command::QueryCurrentTrackSD
941 | Command::QueryCurrentTrackUSBHost
942 | Command::QueryFolderTrackCnt
943 | Command::QueryFolderCnt
944 );
945
946 // Different handling based on feedback mode
947 if self.feedback_enable {
948 // With feedback enabled, we expect an ACK followed by data for queries
949
950 // First read for ACK
951 match self.read_last_message().await {
952 Ok(_) if self.last_cmd_acknowledged => {
953 // ACK received, now we need the data response for queries
954 if is_query_command {
955 #[cfg(feature = "defmt")]
956 info!("Reading data response after ACK for query");
957
958 // Read the data response - use a short delay if needed
959 self.delay.delay_ms(20).await;
960
961 match self.read_last_message().await {
962 Ok(_) => {
963 #[cfg(feature = "defmt")]
964 info!("Received data response for query");
965 }
966 Err(e) => {
967 #[cfg(feature = "defmt")]
968 info!(
969 "Error reading data response: {:?}",
970 Debug2Format(&e)
971 );
972 return Err(e);
973 }
974 }
975 }
976 }
977 Ok(_) => {
978 // Response but no ACK
979 if command_data.command != Command::Reset {
980 #[cfg(feature = "defmt")]
981 info!("Expected ACK not received");
982 return Err(Error::FailedAck);
983 }
984 }
985 Err(e) => return Err(e),
986 }
987 } else {
988 // In non-feedback mode, we need to read the response for query commands
989
990 if is_query_command {
991 // Wait a bit longer for the device to respond
992 self.delay.delay_ms(100).await;
993
994 // Try to read the complete response with retries for fragmented messages
995 let mut attempts = 0;
996 let max_attempts = 3;
997
998 while attempts < max_attempts {
999 attempts += 1;
1000
1001 // Try to read a response
1002 let mut buffer = [0u8; 32]; // Larger buffer to catch more data
1003 let bytes_read = match self.port.read(&mut buffer).await {
1004 Ok(n) => n,
1005 Err(e) => {
1006 #[cfg(feature = "defmt")]
1007 info!("Read error: {:?}", Debug2Format(&e));
1008 if attempts == max_attempts {
1009 return Err(Error::SerialPort(e));
1010 }
1011 self.delay.delay_ms(50).await;
1012 continue;
1013 }
1014 };
1015
1016 #[cfg(feature = "defmt")]
1017 if bytes_read > 0 {
1018 info!(
1019 "Response (attempt {}): {:?}",
1020 attempts,
1021 &buffer[..bytes_read]
1022 );
1023 }
1024
1025 // Check if we have a complete message
1026 if bytes_read >= DATA_FRAME_SIZE
1027 && buffer[0] == START_BYTE
1028 && buffer[DATA_FRAME_SIZE - 1] == END_BYTE
1029 {
1030 // Try to extract command and check
1031 if let Ok(cmd) = Command::try_from(buffer[INDEX_CMD]) {
1032 // Update last response
1033 self.last_response.command = cmd;
1034 self.last_response.param_h = buffer[INDEX_PARAM_H];
1035 self.last_response.param_l = buffer[INDEX_PARAM_L];
1036
1037 // For track count, accept any valid response
1038 if command_data.command == Command::QueryTrackCntSD
1039 {
1040 #[cfg(feature = "defmt")]
1041 info!(
1042 "Got response for track count query: {:?}, value={}",
1043 cmd, buffer[INDEX_PARAM_L]
1044 );
1045 return Ok(());
1046 }
1047
1048 // For other commands, verify it matches what we sent
1049 if cmd == command_data.command {
1050 #[cfg(feature = "defmt")]
1051 info!(
1052 "Got matching response: {:?}, value={}",
1053 cmd, buffer[INDEX_PARAM_L]
1054 );
1055 return Ok(());
1056 } else {
1057 #[cfg(feature = "defmt")]
1058 info!(
1059 "Response command mismatch: expected {:?}, got {:?}",
1060 command_data.command, cmd
1061 );
1062 }
1063 }
1064 }
1065
1066 // If we didn't get a complete message, wait and try again
1067 if attempts < max_attempts {
1068 self.delay.delay_ms(50).await;
1069 }
1070 }
1071
1072 // Special case: for track count query, don't fail if we'll check for delayed response
1073 if command_data.command == Command::QueryTrackCntSD {
1074 #[cfg(feature = "defmt")]
1075 info!(
1076 "No immediate track count response, will check for delayed response"
1077 );
1078 return Ok(());
1079 }
1080
1081 // If we get here, we didn't get a proper response
1082 #[cfg(feature = "defmt")]
1083 info!(
1084 "Failed to get proper response after {} attempts",
1085 max_attempts
1086 );
1087 return Err(Error::BrokenMessage);
1088 } else {
1089 // For non-query commands in non-feedback mode, we don't need to wait for a response
1090 }
1091 }
1092
1093 Ok(())
1094 }
1095
1096 /// Clear any pending data in the receive buffer
1097 ///
1098 /// This method safely reads and discards any data waiting in the receive buffer
1099 /// without blocking indefinitely. It uses non-blocking I/O patterns to avoid
1100 /// hanging when no data is available.
1101 ///
1102 /// This is critical during initialization and after certain commands to ensure
1103 /// a clean communication state and prevent misinterpreting stale data as responses
1104 /// to new commands.
1105 async fn clear_receive_buffer(&mut self) -> Result<(), Error<S::Error>> {
1106 #[cfg(feature = "defmt")]
1107 info!("clear_receive_buffer: Starting buffer clear");
1108 let mut buffer = [0u8; 32];
1109
1110 // Simple approach: try to read once if data is available
1111 #[cfg(feature = "defmt")]
1112 info!("clear_receive_buffer: Checking if data is available");
1113
1114 let has_data = match self.port.read_ready() {
1115 Ok(true) => {
1116 #[cfg(feature = "defmt")]
1117 info!("clear_receive_buffer: Data is available, reading");
1118 true
1119 }
1120 _ => {
1121 #[cfg(feature = "defmt")]
1122 info!("clear_receive_buffer: No data available");
1123 false
1124 }
1125 };
1126
1127 if has_data {
1128 // Try to read the available data
1129 match self.port.read(&mut buffer).await {
1130 Ok(0) => {
1131 #[cfg(feature = "defmt")]
1132 info!("clear_receive_buffer: Read returned 0 bytes");
1133 }
1134 Ok(_n) => {
1135 #[cfg(feature = "defmt")]
1136 info!("Cleared {} bytes: {:?}", _n, &buffer[.._n]);
1137 }
1138 Err(_e) => {
1139 #[cfg(feature = "defmt")]
1140 info!(
1141 "clear_receive_buffer: Read error: {:?}",
1142 Debug2Format(&_e)
1143 );
1144 }
1145 }
1146 }
1147
1148 #[cfg(feature = "defmt")]
1149 info!("clear_receive_buffer: Completed successfully");
1150 Ok(())
1151 }
1152
1153 /// Play the next track
1154 ///
1155 /// Sends the command to play the next track in sequence.
1156 pub async fn next(&mut self) -> Result<(), Error<S::Error>> {
1157 self.send_command(MessageData::new(Command::Next, 0, 0))
1158 .await
1159 }
1160
1161 /// Reset the DFPlayer module
1162 ///
1163 /// Sends a reset command to the module and waits for it to restart.
1164 /// The reset command typically causes the module to restart its processor
1165 /// and reinitialize its state.
1166 ///
1167 /// Special handling is provided for the reset command, as it often won't
1168 /// receive a response from the module.
1169 ///
1170 /// # Arguments
1171 /// * `reset_duration_override` - Optional override for reset delay duration (ms)
1172 pub async fn reset(
1173 &mut self,
1174 reset_duration_override: Option<u64>,
1175 ) -> Result<(), Error<S::Error>> {
1176 // Send reset command
1177 self.send_command(MessageData::new(Command::Reset, 0, 0))
1178 .await?;
1179
1180 // Wait for the device to complete the reset
1181 let wait_ms = reset_duration_override.unwrap_or(1500); // Default timeout based on typical M16P init time
1182 self.delay.delay_ms(wait_ms as u32).await;
1183
1184 // Try to read a response (though one may not come after reset)
1185 let _ = self.read_last_message().await;
1186
1187 Ok(())
1188 }
1189
1190 /// Set the media source for playback
1191 ///
1192 /// Configures which media source the DFPlayer should use for audio files.
1193 /// This method includes an additional delay after sending the command to
1194 /// allow the module time to switch sources and initialize the file system.
1195 ///
1196 /// # Arguments
1197 /// * `playback_source` - The media source to use (SD card, USB, etc.)
1198 pub async fn set_playback_source(
1199 &mut self,
1200 playback_source: PlayBackSource,
1201 ) -> Result<(), Error<S::Error>> {
1202 // Send the command to set playback source
1203 let cmd_result = self
1204 .send_command(MessageData::new(
1205 Command::SetPlaybackSource,
1206 0,
1207 playback_source as u8,
1208 ))
1209 .await;
1210
1211 // Wait for the module to initialize the source
1212 // This delay is needed regardless of command success
1213 self.delay.delay_ms(200).await;
1214
1215 cmd_result
1216 }
1217
1218 /// Set the volume level (0-30)
1219 ///
1220 /// Configures the output volume of the DFPlayer module.
1221 /// Valid range is 0 (silent) to 30 (maximum volume).
1222 ///
1223 /// # Arguments
1224 /// * `volume` - Volume level from 0 (silent) to 30 (maximum)
1225 ///
1226 /// # Errors
1227 /// Returns `Error::BadParameter` if the volume is greater than 30.
1228 pub async fn set_volume(
1229 &mut self,
1230 volume: u8,
1231 ) -> Result<(), Error<S::Error>> {
1232 if volume > 30 {
1233 return Err(Error::BadParameter);
1234 }
1235 self.send_command(MessageData::new(Command::SetVolume, 0, volume))
1236 .await
1237 }
1238
1239 /// Play a specific track by its index number
1240 ///
1241 /// Starts playback of a track by its numerical index.
1242 /// Track numbers typically start at 1, not 0.
1243 ///
1244 /// # Arguments
1245 /// * `track` - Track number (1-2999)
1246 ///
1247 /// # Errors
1248 /// Returns `Error::BadParameter` if the track number is greater than 2999.
1249 pub async fn play(&mut self, track: u16) -> Result<(), Error<S::Error>> {
1250 if track > 2999 {
1251 return Err(Error::BadParameter);
1252 }
1253 self.send_command(MessageData::new(
1254 Command::PlayTrack,
1255 (track >> 8) as u8,
1256 track as u8,
1257 ))
1258 .await
1259 }
1260
1261 /// Set the equalizer mode
1262 ///
1263 /// Configures the audio equalizer preset on the DFPlayer.
1264 ///
1265 /// # Arguments
1266 /// * `equalizer` - Equalizer preset to use
1267 pub async fn set_equalizer(
1268 &mut self,
1269 equalizer: Equalizer,
1270 ) -> Result<(), Error<S::Error>> {
1271 self.send_command(MessageData::new(Command::SetEQ, 0, equalizer as u8))
1272 .await
1273 }
1274
1275 /// Set whether to loop all tracks
1276 ///
1277 /// Enables or disables looping through all tracks.
1278 ///
1279 /// # Arguments
1280 /// * `enable` - Whether to enable looping of all tracks
1281 pub async fn set_loop_all(
1282 &mut self,
1283 enable: bool,
1284 ) -> Result<(), Error<S::Error>> {
1285 self.send_command(MessageData::new(
1286 Command::PlayLoopAll,
1287 0,
1288 if enable { 1 } else { 0 },
1289 ))
1290 .await
1291 }
1292
1293 /// Pause the current playback
1294 ///
1295 /// Pauses any currently playing track.
1296 pub async fn pause(&mut self) -> Result<(), Error<S::Error>> {
1297 self.send_command(MessageData::new(Command::Pause, 0, 0))
1298 .await
1299 }
1300
1301 /// Resume playback
1302 ///
1303 /// Resumes playback of a paused track.
1304 pub async fn resume(&mut self) -> Result<(), Error<S::Error>> {
1305 self.send_command(MessageData::new(Command::Play, 0, 0))
1306 .await
1307 }
1308
1309 /// Play the previous track
1310 ///
1311 /// Plays the track before the current one in sequence.
1312 pub async fn previous(&mut self) -> Result<(), Error<S::Error>> {
1313 self.send_command(MessageData::new(Command::Previous, 0, 0))
1314 .await
1315 }
1316
1317 /// Stop all playback
1318 ///
1319 /// Stops any current playback, including advertisements.
1320 pub async fn stop(&mut self) -> Result<(), Error<S::Error>> {
1321 self.send_command(MessageData::new(Command::Stop, 0, 0))
1322 .await
1323 }
1324
1325 /// Play a track from a specific folder
1326 ///
1327 /// The DFPlayer supports organizing tracks in folders.
1328 /// This command plays a specific track from a specific folder.
1329 /// Note that the DFPlayer can be very sensitive to folder and track
1330 /// naming. Folders should typically be named with two digits (01-99)
1331 /// and tracks with three digits (001-255).
1332 ///
1333 /// # Arguments
1334 /// * `folder` - Folder number (1-99)
1335 /// * `track` - Track number within the folder (1-255)
1336 ///
1337 /// # Errors
1338 /// Returns `Error::BadParameter` if parameters are out of range.
1339 pub async fn play_from_folder(
1340 &mut self,
1341 folder: u8,
1342 track: u8,
1343 ) -> Result<(), Error<S::Error>> {
1344 if folder == 0 || folder > 99 || track == 0 {
1345 return Err(Error::BadParameter);
1346 }
1347
1348 self.send_command(MessageData::new(
1349 Command::PlayTrackInFolder,
1350 folder,
1351 track,
1352 ))
1353 .await
1354 }
1355
1356 /// Play all tracks in a specific folder in a loop
1357 ///
1358 /// This command plays all tracks in a specified folder in sequence,
1359 /// and loops back to the first track when the end is reached.
1360 /// The folder names must be formatted with two digits, starting
1361 /// from 01 to 99 and not exceeding 99. With this command, you can
1362 /// store more than 255 tracks in a folder.
1363 ///
1364 /// # Arguments
1365 /// * `folder` - Folder number (1-99)
1366 ///
1367 /// # Errors
1368 /// Returns `Error::BadParameter` if the folder number is out of range.
1369 pub async fn play_loop_folder(
1370 &mut self,
1371 folder: u8,
1372 ) -> Result<(), Error<S::Error>> {
1373 if folder == 0 || folder > 99 {
1374 return Err(Error::BadParameter);
1375 }
1376
1377 self.send_command(MessageData::new(Command::PlayLoopFolder, 0, folder))
1378 .await
1379 }
1380
1381 /// Play tracks in random order
1382 ///
1383 /// Starts playback in random order from the current source.
1384 pub async fn play_random(&mut self) -> Result<(), Error<S::Error>> {
1385 self.send_command(MessageData::new(Command::PlayRandom, 0, 0))
1386 .await
1387 }
1388
1389 /// Set whether to loop the current track
1390 ///
1391 /// Enables or disables looping of the currently playing track.
1392 ///
1393 /// # Arguments
1394 /// * `enable` - Whether to enable looping of the current track
1395 pub async fn set_loop_current_track(
1396 &mut self,
1397 enable: bool,
1398 ) -> Result<(), Error<S::Error>> {
1399 self.send_command(MessageData::new(
1400 Command::LoopCurrentTrack,
1401 0,
1402 if enable { 1 } else { 0 },
1403 ))
1404 .await
1405 }
1406
1407 /// Query the total number of tracks on the SD card
1408 pub async fn query_tracks_sd(&mut self) -> Result<u16, Error<S::Error>> {
1409 self.send_command(MessageData::new(Command::QueryTrackCntSD, 0, 0))
1410 .await?;
1411 Ok(self.last_response.param_l as u16)
1412 }
1413
1414 /// Query the total number of tracks in a specific folder
1415 pub async fn query_tracks_folder(
1416 &mut self,
1417 folder: u8,
1418 ) -> Result<u16, Error<S::Error>> {
1419 self.send_command(MessageData::new(
1420 Command::QueryFolderTrackCnt,
1421 0,
1422 folder,
1423 ))
1424 .await?;
1425 Ok(self.last_response.param_l as u16)
1426 }
1427
1428 // Query the currently playing track number on the SD card
1429 pub async fn query_current_track_sd(
1430 &mut self,
1431 ) -> Result<u16, Error<S::Error>> {
1432 self.send_command(MessageData::new(Command::QueryCurrentTrackSD, 0, 0))
1433 .await?;
1434 Ok(((self.last_response.param_h as u16) << 8)
1435 | self.last_response.param_l as u16)
1436 }
1437
1438 /// Query the current volume setting
1439 ///
1440 /// Returns the current volume level (0-30) or an error.
1441 pub async fn query_volume(&mut self) -> Result<u8, Error<S::Error>> {
1442 self.send_command(MessageData::new(Command::QueryVolume, 0, 0))
1443 .await?;
1444 Ok(self.last_response.param_l)
1445 }
1446
1447 /// Query the current equalizer setting
1448 ///
1449 /// Returns the current equalizer setting (0-5) or an error.
1450 pub async fn query_eq(&mut self) -> Result<u8, Error<S::Error>> {
1451 self.send_command(MessageData::new(Command::QueryEQ, 0, 0))
1452 .await?;
1453 Ok(self.last_response.param_l)
1454 }
1455
1456 /// Send the device to sleep mode
1457 ///
1458 /// Puts the DFPlayer into low-power sleep mode. In this mode, the device
1459 /// will not respond to most commands until woken up.
1460 ///
1461 /// Note: While in sleep mode, the device will return `ModuleError::Sleeping`
1462 /// for most commands.
1463 pub async fn sleep(&mut self) -> Result<(), Error<S::Error>> {
1464 self.send_command(MessageData::new(Command::EnterSleepMode, 0, 0))
1465 .await
1466 }
1467
1468 /// Wake up the device from sleep mode
1469 ///
1470 /// Attempts to wake up the DFPlayer from sleep mode using the wake up command (0x0B).
1471 /// Since this command is reported to be unreliable on some modules, it can
1472 /// optionally fall back to a reset if specified.
1473 ///
1474 /// # Arguments
1475 /// * `reset_if_needed` - Whether to perform a reset if the normal wake up command fails
1476 /// * `reset_duration_override` - Optional override for reset delay duration (ms) if reset is used
1477 pub async fn wake_up(
1478 &mut self,
1479 reset_if_needed: bool,
1480 reset_duration_override: Option<u64>,
1481 ) -> Result<(), Error<S::Error>> {
1482 // First try the normal wake command
1483 let result = self
1484 .send_command(MessageData::new(Command::EnterNormalMode, 0, 0))
1485 .await;
1486
1487 // If the command succeeded or we don't want to try reset, return the result
1488 if result.is_ok() || !reset_if_needed {
1489 return result;
1490 }
1491
1492 // If we get here, the normal wake command failed and we want to try reset
1493 #[cfg(feature = "defmt")]
1494 info!("Normal wake failed, trying reset");
1495
1496 // Fall back to reset as a more reliable way to wake the device
1497 self.reset(reset_duration_override).await
1498 }
1499
1500 /// Query the current status of the device
1501 ///
1502 /// Returns the current status byte or an error. The status byte indicates:
1503 /// - If a USB disk is inserted (0x01)
1504 /// - If a TF card is inserted (0x02)
1505 /// - If a USB flash drive is inserted (0x04)
1506 /// - If the device is playing (0x08)
1507 /// - If the device is paused (0x10)
1508 ///
1509 /// The returned value is a combination (binary OR) of these flags.
1510 pub async fn query_status(&mut self) -> Result<u8, Error<S::Error>> {
1511 self.send_command(MessageData::new(Command::QueryStatus, 0, 0))
1512 .await?;
1513 Ok(self.last_response.param_l)
1514 }
1515}