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::try_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::{info, Debug2Format};
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 try_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 let mut player = Self {
485 port,
486 feedback_enable,
487 last_command: MessageData::new(Command::EnterNormalMode, 0, 0),
488 last_response: MessageData::new(Command::EnterNormalMode, 0, 0),
489 last_cmd_acknowledged: false,
490 timeout_ms,
491 time_source,
492 delay,
493 };
494
495 // Clear any pending data in the receive buffer first
496 #[cfg(feature = "defmt")]
497 info!("Clearing initial receive buffer");
498 let _ = player.clear_receive_buffer().await;
499
500 // Send reset command with longer timeout for initialization
501 #[cfg(feature = "defmt")]
502 info!("Sending reset command");
503
504 // Store original timeout and use a longer one for reset
505 let original_timeout = player.timeout_ms;
506 player.timeout_ms = 2000; // Longer timeout for reset
507
508 // Send the reset command using the special init version that won't hang
509 let _reset_result = player
510 .send_command_init(MessageData::new(Command::Reset, 0, 0))
511 .await;
512
513 // Wait for device reset regardless of command result
514 let wait_ms = reset_duration_override.unwrap_or(1500);
515 #[cfg(feature = "defmt")]
516 info!("Waiting {}ms for device reset", wait_ms);
517 player.delay.delay_ms(wait_ms as u32).await;
518
519 // Clear any data that might have arrived during reset
520 let _ = player.clear_receive_buffer().await;
521
522 // Restore original timeout
523 player.timeout_ms = original_timeout;
524
525 // Continue even if reset command had issues
526 if let Err(_e) = _reset_result {
527 #[cfg(feature = "defmt")]
528 info!("Reset error: {:?} - continuing anyway", Debug2Format(&_e));
529 }
530
531 // Configure SD card as the default media source
532 #[cfg(feature = "defmt")]
533 info!("Setting playback source to SD card");
534
535 // Use the special init command here too
536 let _source_result = player
537 .send_command_init(MessageData::new(
538 Command::SetPlaybackSource,
539 0,
540 PlayBackSource::SDCard as u8,
541 ))
542 .await;
543
544 if let Err(_e) = _source_result {
545 #[cfg(feature = "defmt")]
546 info!(
547 "Source select warning: {:?} - continuing anyway",
548 Debug2Format(&_e)
549 );
550 }
551
552 // Add a delay after source selection
553 player.delay.delay_ms(200).await;
554
555 // Set initial volume to a moderate level
556 #[cfg(feature = "defmt")]
557 info!("Setting initial volume");
558
559 // Use the special init command here too
560 let _vol_result = player
561 .send_command_init(MessageData::new(Command::SetVolume, 0, 15))
562 .await;
563
564 if let Err(_e) = _vol_result {
565 #[cfg(feature = "defmt")]
566 info!(
567 "Volume set warning: {:?} - continuing anyway",
568 Debug2Format(&_e)
569 );
570 }
571
572 #[cfg(feature = "defmt")]
573 info!("DFPlayer initialization complete");
574
575 Ok(player)
576 }
577
578 /// Read and process a message from the DFPlayer module
579 ///
580 /// This function handles the binary protocol parsing, message validation,
581 /// and stores the last response. It uses a robust state machine to assemble
582 /// complete messages from potentially fragmented reads, with proper timeout
583 /// handling and error recovery.
584 ///
585 /// Special handling is provided for reset commands and 8-byte response formats
586 /// that sometimes occur in feedback mode.
587 ///
588 /// Returns `Ok(())` if a valid message was received and processed, or an error.
589 /// If a module error response was received, returns that specific error.
590 pub async fn read_last_message(&mut self) -> Result<(), Error<S::Error>> {
591 let timeout_start = self.time_source.now();
592
593 // State tracking for message assembly
594 let mut current_index = 0;
595 let mut message = [0u8; DATA_FRAME_SIZE];
596 let mut receive_buffer = [0u8; 32];
597
598 // Continue trying to read until timeout
599 while !self.time_source.is_elapsed(timeout_start, self.timeout_ms) {
600 // Try to read data with graceful fallback if read_ready isn't reliable
601 let bytes_read = match self.port.read_ready() {
602 Ok(true) => match self.port.read(&mut receive_buffer).await {
603 Ok(n) => n,
604 Err(_e) => {
605 #[cfg(feature = "defmt")]
606 info!(
607 "Read error, will retry: {:?}",
608 Debug2Format(&_e)
609 );
610 self.delay.delay_ms(10).await;
611 continue;
612 }
613 },
614 // If read_ready says not ready or errors, we'll still try a read
615 // This helps with UART implementations where read_ready isn't reliable
616 _ => {
617 match self.port.read(&mut receive_buffer).await {
618 Ok(n) => {
619 if n == 0 {
620 // No data available, wait briefly and retry
621 self.delay.delay_ms(10).await;
622 continue;
623 }
624 n
625 }
626 Err(_e) => {
627 #[cfg(feature = "defmt")]
628 info!("Read error: {:?}", Debug2Format(&_e));
629 self.delay.delay_ms(10).await;
630 continue;
631 }
632 }
633 }
634 };
635
636 #[cfg(feature = "defmt")]
637 if bytes_read > 0 {
638 info!(
639 "Read {} bytes: {:?}",
640 bytes_read,
641 &receive_buffer[..bytes_read]
642 );
643 }
644
645 // Special pattern for feedback mode, second responses: 8-byte response format
646 // The DFPlayer sometimes sends truncated 8-byte messages instead of the standard
647 // 10-byte format, particularly for second responses when feedback is enabled.
648 // This appears to be an undocumented protocol quirk of the module.
649 if bytes_read == 8 && receive_buffer[0] == 0x06 {
650 // This looks like a truncated response with the command at index 1
651 let cmd_byte = receive_buffer[1];
652
653 // Try to convert command byte
654 if let Ok(cmd) = Command::try_from(cmd_byte) {
655 self.last_response.command = cmd;
656 self.last_response.param_h = receive_buffer[3];
657 self.last_response.param_l = receive_buffer[4];
658
659 #[cfg(feature = "defmt")]
660 info!(
661 "Parsed 8-byte response: cmd={:?}, params={},{}",
662 cmd,
663 self.last_response.param_h,
664 self.last_response.param_l
665 );
666
667 return Ok(());
668 }
669 }
670
671 // Process each byte through our state machine
672 for i in 0..bytes_read {
673 let byte = receive_buffer[i];
674
675 match current_index {
676 0 => {
677 // State 0: Looking for start byte (0x7E)
678 if byte == START_BYTE {
679 message[current_index] = byte;
680 current_index = 1;
681 }
682 }
683 9 => {
684 // State 9: We have 9 bytes and are looking for the end byte (0xEF)
685 message[current_index] = byte;
686
687 if byte == END_BYTE {
688 // Complete message received, validate and process
689 #[cfg(feature = "defmt")]
690 info!("Complete message: {:?}", message);
691
692 // Validate checksum
693 let read_checksum = ((message[7] as u16) << 8)
694 | (message[8] as u16);
695 let calc_checksum = checksum(&message[1..7]);
696
697 if read_checksum == calc_checksum {
698 // Valid message - extract command and parameters
699 if let Ok(cmd) = Command::try_from(message[3]) {
700 self.last_response.command = cmd;
701 self.last_response.param_h = message[5];
702 self.last_response.param_l = message[6];
703
704 // Check if this is an ACK message
705 if self.last_response == ACK_MESSAGE_DATA {
706 self.last_cmd_acknowledged = true;
707 }
708
709 // Check if this is an error response
710 if self.last_response.command
711 == Command::NotifyError
712 {
713 if let Ok(err) = ModuleError::try_from(
714 self.last_response.param_l,
715 ) {
716 return Err(Error::ModuleError(
717 err,
718 ));
719 } else {
720 return Err(Error::Unknown);
721 }
722 }
723
724 // Valid message processed successfully
725 return Ok(());
726 }
727 } else {
728 #[cfg(feature = "defmt")]
729 info!(
730 "Checksum mismatch: expected {:04X}, got {:04X}",
731 calc_checksum, read_checksum
732 );
733 }
734 }
735
736 // Reset parser state whether valid or not
737 current_index = 0;
738 }
739 _ => {
740 // States 1-8: Collecting the message body
741 message[current_index] = byte;
742 current_index += 1;
743 }
744 }
745 }
746 }
747
748 // If we reached here, we timed out with no valid response
749 #[cfg(feature = "defmt")]
750 info!("Timeout waiting for response");
751
752 // Special case for reset command - just return success
753 if self.last_command.command == Command::Reset {
754 #[cfg(feature = "defmt")]
755 info!("Ignoring timeout for reset command");
756 return Ok(());
757 }
758
759 // Timeout error for other commands
760 Err(Error::UserTimeout)
761 }
762
763 /// Special version of send_command that won't fail if responses aren't received
764 ///
765 /// Used during initialization to improve reliability when the module is first starting up.
766 /// Unlike the regular send_command, this method:
767 /// - Uses shorter timeouts
768 /// - Continues even if responses aren't received
769 /// - Employs simplified error handling
770 ///
771 /// # Arguments
772 /// * `command_data` - The command and parameters to send
773 async fn send_command_init(
774 &mut self,
775 command_data: MessageData,
776 ) -> Result<(), Error<S::Error>> {
777 // Format the command message according to protocol
778 let mut out_buffer = [
779 START_BYTE, VERSION, MSG_LEN, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
780 END_BYTE,
781 ];
782
783 // Set feedback flag if enabled
784 if self.feedback_enable {
785 out_buffer[INDEX_FEEDBACK_ENABLE] = 0x1;
786 }
787
788 // Set command and parameters
789 out_buffer[INDEX_CMD] = command_data.command as u8;
790 out_buffer[INDEX_PARAM_H] = command_data.param_h;
791 out_buffer[INDEX_PARAM_L] = command_data.param_l;
792
793 // Calculate and set checksum
794 let checksum = checksum(&out_buffer[INDEX_VERSION..INDEX_CHECKSUM_H]);
795 out_buffer[INDEX_CHECKSUM_H] = (checksum >> 8) as u8;
796 out_buffer[INDEX_CHECKSUM_L] = checksum as u8;
797
798 // Log the message being sent
799 #[cfg(feature = "defmt")]
800 info!("tx {}", out_buffer);
801
802 // Send the message to the device
803 self.port
804 .write_all(&out_buffer)
805 .await
806 .map_err(Error::SerialPort)?;
807
808 // Store the command for reference
809 self.last_command = command_data;
810 self.last_cmd_acknowledged = false;
811
812 // If feedback is disabled, don't even try to read a response during initialization
813 if !self.feedback_enable {
814 #[cfg(feature = "defmt")]
815 info!(
816 "Skipping response wait during initialization (feedback disabled)"
817 );
818
819 // Still need a small delay to let the command be processed
820 self.delay.delay_ms(50).await;
821 return Ok(());
822 }
823
824 // For feedback mode, use a short timeout for reading during initialization
825 let original_timeout = self.timeout_ms;
826 self.timeout_ms = 200; // Short timeout for init
827
828 // Try to read a response but don't fail if we timeout
829 let result = self.read_last_message().await;
830
831 // Restore original timeout
832 self.timeout_ms = original_timeout;
833
834 // During initialization, continue even if we get a timeout
835 match result {
836 Ok(_) => {
837 #[cfg(feature = "defmt")]
838 info!("Initialization command received response");
839 }
840 Err(Error::UserTimeout) => {
841 #[cfg(feature = "defmt")]
842 info!("Initialization command timed out (continuing anyway)");
843 }
844 Err(_e) => {
845 #[cfg(feature = "defmt")]
846 info!("Initialization command error: {:?}", Debug2Format(&_e));
847 }
848 }
849
850 Ok(())
851 }
852
853 /// Send a command to the DFPlayer module
854 ///
855 /// This constructs a properly formatted message, sends it to the device,
856 /// and then waits for a response or acknowledgement if feedback is enabled.
857 /// For query commands in non-feedback mode, attempts to read and process responses
858 /// with multiple retries if needed.
859 ///
860 /// The method calculates the appropriate checksum and handles all aspects of
861 /// the binary communication protocol.
862 ///
863 /// # Arguments
864 /// * `command_data` - The command and parameters to send
865 pub async fn send_command(
866 &mut self,
867 command_data: MessageData,
868 ) -> Result<(), Error<S::Error>> {
869 // Format the command message according to protocol
870 let mut out_buffer = [
871 START_BYTE, VERSION, MSG_LEN, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
872 END_BYTE,
873 ];
874
875 // Set feedback flag if enabled
876 if self.feedback_enable {
877 out_buffer[INDEX_FEEDBACK_ENABLE] = 0x1;
878 }
879
880 // Set command and parameters
881 out_buffer[INDEX_CMD] = command_data.command as u8;
882 out_buffer[INDEX_PARAM_H] = command_data.param_h;
883 out_buffer[INDEX_PARAM_L] = command_data.param_l;
884
885 // Calculate and set checksum
886 let checksum = checksum(&out_buffer[INDEX_VERSION..INDEX_CHECKSUM_H]);
887 out_buffer[INDEX_CHECKSUM_H] = (checksum >> 8) as u8;
888 out_buffer[INDEX_CHECKSUM_L] = checksum as u8;
889
890 // Log the message being sent
891 #[cfg(feature = "defmt")]
892 info!("tx {}", out_buffer);
893
894 // Send the message to the device
895 self.port
896 .write_all(&out_buffer)
897 .await
898 .map_err(Error::SerialPort)?;
899
900 // Store the command for reference
901 self.last_command = command_data;
902 self.last_cmd_acknowledged = false;
903
904 // Determine if this is a query command
905 let is_query_command = matches!(
906 command_data.command,
907 Command::QueryTrackCntSD
908 | Command::QueryVolume
909 | Command::QueryEQ
910 | Command::QueryAvailableSources
911 | Command::QueryStatus
912 | Command::QueryTrackCntUSB
913 | Command::QueryCurrentTrackUSBFlash
914 | Command::QueryCurrentTrackSD
915 | Command::QueryCurrentTrackUSBHost
916 | Command::QueryFolderTrackCnt
917 | Command::QueryFolderCnt
918 );
919
920 // Different handling based on feedback mode
921 if self.feedback_enable {
922 // With feedback enabled, we expect an ACK followed by data for queries
923
924 // First read for ACK
925 match self.read_last_message().await {
926 Ok(_) if self.last_cmd_acknowledged => {
927 // ACK received, now we need the data response for queries
928 if is_query_command {
929 #[cfg(feature = "defmt")]
930 info!("Reading data response after ACK for query");
931
932 // Read the data response - use a short delay if needed
933 self.delay.delay_ms(20).await;
934
935 match self.read_last_message().await {
936 Ok(_) => {
937 #[cfg(feature = "defmt")]
938 info!("Received data response for query");
939 }
940 Err(e) => {
941 #[cfg(feature = "defmt")]
942 info!(
943 "Error reading data response: {:?}",
944 Debug2Format(&e)
945 );
946 return Err(e);
947 }
948 }
949 }
950 }
951 Ok(_) => {
952 // Response but no ACK
953 if command_data.command != Command::Reset {
954 #[cfg(feature = "defmt")]
955 info!("Expected ACK not received");
956 return Err(Error::FailedAck);
957 }
958 }
959 Err(e) => return Err(e),
960 }
961 } else {
962 // In non-feedback mode, we need to read the response for query commands
963
964 if is_query_command {
965 // Wait a bit longer for the device to respond
966 self.delay.delay_ms(100).await;
967
968 // Try to read the complete response with retries for fragmented messages
969 let mut attempts = 0;
970 let max_attempts = 3;
971
972 while attempts < max_attempts {
973 attempts += 1;
974
975 // Try to read a response
976 let mut buffer = [0u8; 32]; // Larger buffer to catch more data
977 let bytes_read = match self.port.read(&mut buffer).await {
978 Ok(n) => n,
979 Err(e) => {
980 #[cfg(feature = "defmt")]
981 info!("Read error: {:?}", Debug2Format(&e));
982 if attempts == max_attempts {
983 return Err(Error::SerialPort(e));
984 }
985 self.delay.delay_ms(50).await;
986 continue;
987 }
988 };
989
990 #[cfg(feature = "defmt")]
991 if bytes_read > 0 {
992 info!(
993 "Response (attempt {}): {:?}",
994 attempts,
995 &buffer[..bytes_read]
996 );
997 }
998
999 // Check if we have a complete message
1000 if bytes_read >= DATA_FRAME_SIZE
1001 && buffer[0] == START_BYTE
1002 && buffer[DATA_FRAME_SIZE - 1] == END_BYTE
1003 {
1004 // Try to extract command and check
1005 if let Ok(cmd) = Command::try_from(buffer[INDEX_CMD]) {
1006 // Update last response
1007 self.last_response.command = cmd;
1008 self.last_response.param_h = buffer[INDEX_PARAM_H];
1009 self.last_response.param_l = buffer[INDEX_PARAM_L];
1010
1011 // For track count, accept any valid response
1012 if command_data.command == Command::QueryTrackCntSD
1013 {
1014 #[cfg(feature = "defmt")]
1015 info!(
1016 "Got response for track count query: {:?}, value={}",
1017 cmd, buffer[INDEX_PARAM_L]
1018 );
1019 return Ok(());
1020 }
1021
1022 // For other commands, verify it matches what we sent
1023 if cmd == command_data.command {
1024 #[cfg(feature = "defmt")]
1025 info!(
1026 "Got matching response: {:?}, value={}",
1027 cmd, buffer[INDEX_PARAM_L]
1028 );
1029 return Ok(());
1030 } else {
1031 #[cfg(feature = "defmt")]
1032 info!(
1033 "Response command mismatch: expected {:?}, got {:?}",
1034 command_data.command, cmd
1035 );
1036 }
1037 }
1038 }
1039
1040 // If we didn't get a complete message, wait and try again
1041 if attempts < max_attempts {
1042 self.delay.delay_ms(50).await;
1043 }
1044 }
1045
1046 // Special case: for track count query, don't fail if we'll check for delayed response
1047 if command_data.command == Command::QueryTrackCntSD {
1048 #[cfg(feature = "defmt")]
1049 info!(
1050 "No immediate track count response, will check for delayed response"
1051 );
1052 return Ok(());
1053 }
1054
1055 // If we get here, we didn't get a proper response
1056 #[cfg(feature = "defmt")]
1057 info!(
1058 "Failed to get proper response after {} attempts",
1059 max_attempts
1060 );
1061 return Err(Error::BrokenMessage);
1062 } else {
1063 // For non-query commands in non-feedback mode, we don't need to wait for a response
1064 }
1065 }
1066
1067 Ok(())
1068 }
1069
1070 /// Clear any pending data in the receive buffer
1071 ///
1072 /// This method safely reads and discards any data waiting in the receive buffer
1073 /// without blocking indefinitely. It uses non-blocking I/O patterns to avoid
1074 /// hanging when no data is available.
1075 ///
1076 /// This is critical during initialization and after certain commands to ensure
1077 /// a clean communication state and prevent misinterpreting stale data as responses
1078 /// to new commands.
1079 async fn clear_receive_buffer(&mut self) -> Result<(), Error<S::Error>> {
1080 let mut buffer = [0u8; 32];
1081 let start = self.time_source.now();
1082
1083 // Try reading a few times with timeouts
1084 for _ in 0..5 {
1085 // First check if data is available to avoid blocking
1086 let has_data = match self.port.read_ready() {
1087 Ok(ready) => ready,
1088 Err(_) => false, // Assume no data on error
1089 };
1090
1091 if !has_data {
1092 // No data available, don't block
1093 #[cfg(feature = "defmt")]
1094 info!("No data to clear");
1095
1096 // Short delay and continue
1097 self.delay.delay_ms(10).await;
1098 continue;
1099 }
1100
1101 // Data is available, read and discard it
1102 match self.port.read(&mut buffer).await {
1103 Ok(0) => {
1104 // No data was actually read
1105 self.delay.delay_ms(10).await;
1106 }
1107 Ok(_n) => {
1108 #[cfg(feature = "defmt")]
1109 info!("Cleared {} bytes: {:?}", n, &buffer[.._n]);
1110 // Short delay and try again
1111 self.delay.delay_ms(10).await;
1112 }
1113 Err(_e) => {
1114 #[cfg(feature = "defmt")]
1115 info!("Clear buffer read error: {:?}", Debug2Format(&_e));
1116 self.delay.delay_ms(10).await;
1117 }
1118 }
1119
1120 // Check if we've been trying too long
1121 if self.time_source.is_elapsed(start, 100) {
1122 break;
1123 }
1124 }
1125
1126 Ok(())
1127 }
1128
1129 /// Play the next track
1130 ///
1131 /// Sends the command to play the next track in sequence.
1132 pub async fn next(&mut self) -> Result<(), Error<S::Error>> {
1133 self.send_command(MessageData::new(Command::Next, 0, 0))
1134 .await
1135 }
1136
1137 /// Reset the DFPlayer module
1138 ///
1139 /// Sends a reset command to the module and waits for it to restart.
1140 /// The reset command typically causes the module to restart its processor
1141 /// and reinitialize its state.
1142 ///
1143 /// Special handling is provided for the reset command, as it often won't
1144 /// receive a response from the module.
1145 ///
1146 /// # Arguments
1147 /// * `reset_duration_override` - Optional override for reset delay duration (ms)
1148 pub async fn reset(
1149 &mut self,
1150 reset_duration_override: Option<u64>,
1151 ) -> Result<(), Error<S::Error>> {
1152 // Send reset command
1153 self.send_command(MessageData::new(Command::Reset, 0, 0))
1154 .await?;
1155
1156 // Wait for the device to complete the reset
1157 let wait_ms = reset_duration_override.unwrap_or(1500); // Default timeout based on typical M16P init time
1158 self.delay.delay_ms(wait_ms as u32).await;
1159
1160 // Try to read a response (though one may not come after reset)
1161 let _ = self.read_last_message().await;
1162
1163 Ok(())
1164 }
1165
1166 /// Set the media source for playback
1167 ///
1168 /// Configures which media source the DFPlayer should use for audio files.
1169 /// This method includes an additional delay after sending the command to
1170 /// allow the module time to switch sources and initialize the file system.
1171 ///
1172 /// # Arguments
1173 /// * `playback_source` - The media source to use (SD card, USB, etc.)
1174 pub async fn set_playback_source(
1175 &mut self,
1176 playback_source: PlayBackSource,
1177 ) -> Result<(), Error<S::Error>> {
1178 // Send the command to set playback source
1179 let cmd_result = self
1180 .send_command(MessageData::new(
1181 Command::SetPlaybackSource,
1182 0,
1183 playback_source as u8,
1184 ))
1185 .await;
1186
1187 // Wait for the module to initialize the source
1188 // This delay is needed regardless of command success
1189 self.delay.delay_ms(200).await;
1190
1191 cmd_result
1192 }
1193
1194 /// Set the volume level (0-30)
1195 ///
1196 /// Configures the output volume of the DFPlayer module.
1197 /// Valid range is 0 (silent) to 30 (maximum volume).
1198 ///
1199 /// # Arguments
1200 /// * `volume` - Volume level from 0 (silent) to 30 (maximum)
1201 ///
1202 /// # Errors
1203 /// Returns `Error::BadParameter` if the volume is greater than 30.
1204 pub async fn set_volume(
1205 &mut self,
1206 volume: u8,
1207 ) -> Result<(), Error<S::Error>> {
1208 if volume > 30 {
1209 return Err(Error::BadParameter);
1210 }
1211 self.send_command(MessageData::new(Command::SetVolume, 0, volume))
1212 .await
1213 }
1214
1215 /// Play a specific track by its index number
1216 ///
1217 /// Starts playback of a track by its numerical index.
1218 /// Track numbers typically start at 1, not 0.
1219 ///
1220 /// # Arguments
1221 /// * `track` - Track number (1-2999)
1222 ///
1223 /// # Errors
1224 /// Returns `Error::BadParameter` if the track number is greater than 2999.
1225 pub async fn play(&mut self, track: u16) -> Result<(), Error<S::Error>> {
1226 if track > 2999 {
1227 return Err(Error::BadParameter);
1228 }
1229 self.send_command(MessageData::new(
1230 Command::PlayTrack,
1231 (track >> 8) as u8,
1232 track as u8,
1233 ))
1234 .await
1235 }
1236
1237 /// Set the equalizer mode
1238 ///
1239 /// Configures the audio equalizer preset on the DFPlayer.
1240 ///
1241 /// # Arguments
1242 /// * `equalizer` - Equalizer preset to use
1243 pub async fn set_equalizer(
1244 &mut self,
1245 equalizer: Equalizer,
1246 ) -> Result<(), Error<S::Error>> {
1247 self.send_command(MessageData::new(Command::SetEQ, 0, equalizer as u8))
1248 .await
1249 }
1250
1251 /// Set whether to loop all tracks
1252 ///
1253 /// Enables or disables looping through all tracks.
1254 ///
1255 /// # Arguments
1256 /// * `enable` - Whether to enable looping of all tracks
1257 pub async fn set_loop_all(
1258 &mut self,
1259 enable: bool,
1260 ) -> Result<(), Error<S::Error>> {
1261 self.send_command(MessageData::new(
1262 Command::PlayLoopAll,
1263 0,
1264 if enable { 1 } else { 0 },
1265 ))
1266 .await
1267 }
1268
1269 /// Pause the current playback
1270 ///
1271 /// Pauses any currently playing track.
1272 pub async fn pause(&mut self) -> Result<(), Error<S::Error>> {
1273 self.send_command(MessageData::new(Command::Pause, 0, 0))
1274 .await
1275 }
1276
1277 /// Resume playback
1278 ///
1279 /// Resumes playback of a paused track.
1280 pub async fn resume(&mut self) -> Result<(), Error<S::Error>> {
1281 self.send_command(MessageData::new(Command::Play, 0, 0))
1282 .await
1283 }
1284
1285 /// Play the previous track
1286 ///
1287 /// Plays the track before the current one in sequence.
1288 pub async fn previous(&mut self) -> Result<(), Error<S::Error>> {
1289 self.send_command(MessageData::new(Command::Previous, 0, 0))
1290 .await
1291 }
1292
1293 /// Stop all playback
1294 ///
1295 /// Stops any current playback, including advertisements.
1296 pub async fn stop(&mut self) -> Result<(), Error<S::Error>> {
1297 self.send_command(MessageData::new(Command::Stop, 0, 0))
1298 .await
1299 }
1300
1301 /// Play a track from a specific folder
1302 ///
1303 /// The DFPlayer supports organizing tracks in folders.
1304 /// This command plays a specific track from a specific folder.
1305 ///
1306 /// # Arguments
1307 /// * `folder` - Folder number (1-99)
1308 /// * `track` - Track number within the folder (1-255)
1309 ///
1310 /// # Errors
1311 /// Returns `Error::BadParameter` if parameters are out of range.
1312 pub async fn play_from_folder(
1313 &mut self,
1314 folder: u8,
1315 track: u8,
1316 ) -> Result<(), Error<S::Error>> {
1317 if folder == 0 || folder > 99 || track == 0 {
1318 return Err(Error::BadParameter);
1319 }
1320
1321 self.send_command(MessageData::new(
1322 Command::PlayTrackInFolder,
1323 folder,
1324 track,
1325 ))
1326 .await
1327 }
1328
1329 /// Play tracks in random order
1330 ///
1331 /// Starts playback in random order from the current source.
1332 pub async fn play_random(&mut self) -> Result<(), Error<S::Error>> {
1333 self.send_command(MessageData::new(Command::PlayRandom, 0, 0))
1334 .await
1335 }
1336
1337 /// Set whether to loop the current track
1338 ///
1339 /// Enables or disables looping of the currently playing track.
1340 ///
1341 /// # Arguments
1342 /// * `enable` - Whether to enable looping of the current track
1343 pub async fn set_loop_current_track(
1344 &mut self,
1345 enable: bool,
1346 ) -> Result<(), Error<S::Error>> {
1347 self.send_command(MessageData::new(
1348 Command::LoopCurrentTrack,
1349 0,
1350 if enable { 1 } else { 0 },
1351 ))
1352 .await
1353 }
1354
1355 /// Query the total number of tracks on the SD card
1356 ///
1357 /// Returns the number of tracks on the SD card.
1358 ///
1359 /// This method implements a robust strategy for obtaining track counts:
1360 /// - Sends the query command and processes immediate responses
1361 /// - For non-feedback mode, waits for delayed responses with multiple attempts
1362 /// - Returns 0 if no tracks are found or the count couldn't be retrieved
1363 ///
1364 /// The track count retrieval is one of the most timing-sensitive operations
1365 /// of the DFPlayer module and may require multiple attempts.
1366 pub async fn query_tracks_sd(&mut self) -> Result<u16, Error<S::Error>> {
1367 // Send the command
1368 let result = self
1369 .send_command(MessageData::new(Command::QueryTrackCntSD, 0, 0))
1370 .await;
1371
1372 // If we got a command error that wasn't BrokenMessage, return it
1373 if let Err(e) = result {
1374 match e {
1375 Error::BrokenMessage => {
1376 // We'll continue and try to get a delayed response
1377 #[cfg(feature = "defmt")]
1378 info!(
1379 "Initial track count query failed, trying delayed response"
1380 );
1381 }
1382 _ => return Err(e),
1383 }
1384 }
1385
1386 // If we're in non-feedback mode, wait for delayed response
1387 if !self.feedback_enable {
1388 // Wait longer for delayed response - often track count takes a while
1389 self.delay.delay_ms(500).await;
1390
1391 // Try to read the delayed response with multiple attempts
1392 for attempt in 1..=3 {
1393 let mut buffer = [0u8; 32];
1394 let bytes_read = match self.port.read(&mut buffer).await {
1395 Ok(n) => n,
1396 Err(_) => {
1397 // If we can't read, wait and try again
1398 self.delay.delay_ms(100).await;
1399 continue;
1400 }
1401 };
1402
1403 #[cfg(feature = "defmt")]
1404 if bytes_read > 0 {
1405 info!(
1406 "Delayed response (attempt {}): {:?}",
1407 attempt,
1408 &buffer[..bytes_read]
1409 );
1410 }
1411
1412 // Check for track count response in the buffer
1413 for i in 0..bytes_read.saturating_sub(9) {
1414 if buffer[i] == START_BYTE
1415 && buffer[i + INDEX_CMD]
1416 == Command::QueryTrackCntSD as u8
1417 && buffer[i + 9] == END_BYTE
1418 {
1419 // Found track count response
1420 let track_count = buffer[i + INDEX_PARAM_L];
1421
1422 #[cfg(feature = "defmt")]
1423 info!("Found delayed track count: {}", track_count);
1424
1425 // Update last_response
1426 self.last_response.command = Command::QueryTrackCntSD;
1427 self.last_response.param_h = buffer[i + INDEX_PARAM_H];
1428 self.last_response.param_l = track_count;
1429
1430 return Ok(track_count as u16);
1431 }
1432 }
1433
1434 // If we didn't find a response, wait and try again
1435 if attempt < 3 {
1436 self.delay.delay_ms(150).await;
1437 }
1438 }
1439 }
1440
1441 // If we have a track count response, use it
1442 if self.last_response.command == Command::QueryTrackCntSD {
1443 return Ok(self.last_response.param_l as u16);
1444 }
1445
1446 // No valid track count found
1447 #[cfg(feature = "defmt")]
1448 info!("Failed to get track count after multiple attempts");
1449
1450 // For the special case of no tracks found, return 0
1451 Ok(0)
1452 }
1453
1454 /// Query the current volume setting
1455 ///
1456 /// Returns the current volume level (0-30) or an error.
1457 pub async fn query_volume(&mut self) -> Result<u8, Error<S::Error>> {
1458 self.send_command(MessageData::new(Command::QueryVolume, 0, 0))
1459 .await?;
1460 Ok(self.last_response.param_l)
1461 }
1462
1463 /// Query the current equalizer setting
1464 ///
1465 /// Returns the current equalizer setting (0-5) or an error.
1466 pub async fn query_eq(&mut self) -> Result<u8, Error<S::Error>> {
1467 self.send_command(MessageData::new(Command::QueryEQ, 0, 0))
1468 .await?;
1469 Ok(self.last_response.param_l)
1470 }
1471}