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}