From 36ab6b4290e2e46e947c75bf4857711a1d44a863 Mon Sep 17 00:00:00 2001 From: "aitor c. urrutia aranburu" Date: Wed, 24 Jun 2026 18:20:48 +0200 Subject: [PATCH] Soinua jotzen du --- boot_out.txt | 3 + code.py | 21 ++ lib/audioplayer.py | 522 +++++++++++++++++++++++++++++++++++++++++++++ sd/placeholder.txt | 1 + settings.toml | 0 5 files changed, 547 insertions(+) create mode 100644 boot_out.txt create mode 100644 code.py create mode 100644 lib/audioplayer.py create mode 100644 sd/placeholder.txt create mode 100644 settings.toml diff --git a/boot_out.txt b/boot_out.txt new file mode 100644 index 0000000..6df18b3 --- /dev/null +++ b/boot_out.txt @@ -0,0 +1,3 @@ +Adafruit CircuitPython 10.2.1 on 2026-05-13; Raspberry Pi Pico with rp2040 +Board ID:raspberry_pi_pico +UID:E6614C311B196825 diff --git a/code.py b/code.py new file mode 100644 index 0000000..8bccb08 --- /dev/null +++ b/code.py @@ -0,0 +1,21 @@ +import board +import busio + +from lib.audioplayer import AudioPlayer + +print("Hello, world!") + +uart = busio.UART( + tx=board.GP0, + rx=board.GP1, + baudrate=9600 +) + +player = AudioPlayer(uart) + +print("Volumen:", player.get_volume()) + +player.set_volume(10) + +player.play_index(1) + diff --git a/lib/audioplayer.py b/lib/audioplayer.py new file mode 100644 index 0000000..272d897 --- /dev/null +++ b/lib/audioplayer.py @@ -0,0 +1,522 @@ +import time + +class AudioPlayerError(Exception): + pass + +class PlayStatus: + STOPPED = 0 + PLAYING = 1 + PAUSED = 2 + ERROR = 0xFF + +class PlayMode: + ALL_LOOP = 0 + SINGLE_LOOP = 1 + FOLDER_LOOP = 2 + RANDOM = 3 + SINGLE_STOP = 4 + ALL_ONCE = 5 + FOLDER_ONCE = 6 + ERROR = 0xFF + +class StorageDevice: + UDISK = 1 + SD = 2 + FLASH = 3 + UDISK_OR_SD = 4 + FLASH_OR_UDISK = 5 + FLASH_OR_SD = 6 + ERROR = 0xFF + +class PlayDevice: + UDISK = 0 + SD = 1 + FLASH = 2 + ERROR = 0xFF + +class AudioPlayer: + + DEFAULT_TIMEOUT_MS = 500 + + def __init__(self, uart, debug=False): + self.uart = uart + self.debug = debug + self._last_command = None + self._expected = None + + # --------------------------------------------------------- + # Low level + # --------------------------------------------------------- + + def _flush_rx(self): + while self.uart.in_waiting: + self.uart.read() + + def _build_frame(self, command, data=b""): + frame = bytearray() + + frame.append(command) + frame.append((~command) & 0xFF) + frame.append(len(data)) + frame.extend(data) + + checksum = sum(frame) & 0xFF + frame.append(checksum) + + return frame + + def _send_command( + self, + command, + data=b"", + expected=None, + ): + + self._flush_rx() + + frame = self._build_frame(command, data) + + self._last_command = command + self._expected = expected + + if self.debug: + print( + "TX:", + " ".join(f"{b:02X}" for b in frame) + ) + + self.uart.write(frame) + + def _read_response(self): + + if self.uart.in_waiting < 6: + return None + + raw = self.uart.read(min(self.uart.in_waiting, 32)) + + if raw is None: + return None + + data = bytes(raw) + + if self.debug: + print( + "RX:", + " ".join(f"{b:02X}" for b in data) + ) + + cmd_ok = ( + ( + data[0] == self._last_command + and + data[1] == ((~self._last_command) & 0xFF) + ) + or + ( + self._last_command == 0x05 + and + data[0] == 0x0A + ) + ) + + if not cmd_ok: + return None + + if self._expected is not None: + + if len(data) < 5: + return None + + if ( + data[2] != self._expected[0] + or + data[3] != self._expected[1] + ): + return None + + checksum = sum(data[:-1]) & 0xFF + + if checksum != data[-1]: + return None + + payload = data[4:-1] + + return payload + + def _transaction( + self, + command, + data=b"", + expected=None, + timeout_ms=DEFAULT_TIMEOUT_MS, + ): + + self._send_command( + command, + data, + expected, + ) + + start = time.monotonic() + + while True: + + payload = self._read_response() + + if payload is not None: + return payload + + elapsed_ms = ( + time.monotonic() - start + ) * 1000 + + if elapsed_ms > timeout_ms: + raise AudioPlayerError( + "Response timeout" + ) + + time.sleep(0.01) + + @staticmethod + def _u16(payload): + return (payload[0] << 8) | payload[1] + + # --------------------------------------------------------- + # Device checks + # --------------------------------------------------------- + + def get_storage_device(self): + + payload = self._transaction( + 0x04, + bytes([0x08]), + expected=(0x02, 0x08), + ) + + return payload[0] + + def is_ready(self): + return self.get_storage_device() == StorageDevice.SD + + # --------------------------------------------------------- + # Playback control + # --------------------------------------------------------- + + def status(self): + + payload = self._transaction( + 0x04, + bytes([0x00]), + expected=(0x02, 0x00), + ) + + return payload[0] + + def play(self): + + payload = self._transaction( + 0x04, + bytes([0x01]), + expected=(0x02, 0x00), + ) + + return payload[0] + + def pause(self): + + payload = self._transaction( + 0x04, + bytes([0x02]), + expected=(0x02, 0x00), + ) + + return payload[0] + + def stop(self): + + payload = self._transaction( + 0x04, + bytes([0x03]), + expected=(0x02, 0x00), + ) + + return payload[0] + + def next(self): + + payload = self._transaction( + 0x04, + bytes([0x05]), + expected=(0x03, 0x0E), + timeout_ms=600, + ) + + return self._u16(payload) + + def previous(self): + + payload = self._transaction( + 0x04, + bytes([0x04]), + expected=(0x03, 0x0E), + timeout_ms=600, + ) + + return self._u16(payload) + + def play_index(self, index): + + payload = self._transaction( + 0x04, + bytes([ + 0x06, + (index >> 8) & 0xFF, + index & 0xFF + ]), + expected=(0x03, 0x0E), + ) + + return self._u16(payload) + + def play_name(self, filename): + + payload = self._transaction( + 0x04, + bytes([0x07]) + filename.encode("ascii"), + expected=(0x03, 0x0E), + ) + + return payload[0] + + # --------------------------------------------------------- + # Volume + # --------------------------------------------------------- + + def get_volume(self): + + payload = self._transaction( + 0x06, + bytes([0x00]), + expected=(0x02, 0x00), + timeout_ms=600, + ) + + return payload[0] + + def set_volume(self, volume): + + self._send_command( + 0x06, + bytes([ + 0x01, + volume + ]) + ) + + time.sleep(0.1) + + def volume_up(self): + + self._send_command( + 0x06, + bytes([0x02]) + ) + + time.sleep(0.1) + + def volume_down(self): + + self._send_command( + 0x06, + bytes([0x03]) + ) + + time.sleep(0.1) + + # --------------------------------------------------------- + # Device info + # --------------------------------------------------------- + + def get_play_device(self): + + payload = self._transaction( + 0x04, + bytes([0x09]), + expected=(0x02, 0x09), + ) + + return payload[0] + + def get_total_tracks(self): + + payload = self._transaction( + 0x04, + bytes([0x0D]), + expected=(0x03, 0x0D), + ) + + return self._u16(payload) + + def get_current_track(self): + + payload = self._transaction( + 0x04, + bytes([0x0E]), + expected=(0x03, 0x0E), + ) + + return self._u16(payload) + + def get_current_path_count(self): + + payload = self._transaction( + 0x04, + bytes([0x18]), + expected=(0x03, 0x18), + ) + + return self._u16(payload) + + # --------------------------------------------------------- + # Time functions + # --------------------------------------------------------- + + def get_total_play_time(self): + + payload = self._transaction( + 0x05, + bytes([0x00]), + expected=(0x04, 0x00), + ) + + if len(payload) < 3: + raise AudioPlayerError( + "Invalid time response" + ) + + return ( + payload[0], + payload[1], + payload[2] + ) + + def seek_current(self, minute, second): + + self._send_command( + 0x04, + bytes([ + 0x0F, + minute, + second + ]) + ) + + time.sleep(0.1) + + def play_from_time( + self, + index, + minute, + second + ): + + self._send_command( + 0x04, + bytes([ + 0x10, + (index >> 8) & 0xFF, + index & 0xFF, + minute, + second + ]) + ) + + time.sleep(0.1) + + # --------------------------------------------------------- + # Directories + # --------------------------------------------------------- + + def next_directory(self): + self._send_command(0x04, bytes([0x13])) + time.sleep(0.1) + + def previous_directory(self): + self._send_command(0x04, bytes([0x12])) + time.sleep(0.1) + + # --------------------------------------------------------- + # Play modes + # --------------------------------------------------------- + + def get_play_mode(self): + + payload = self._transaction( + 0x0B, + bytes([0x00]), + expected=(0x02, 0x00), + ) + + return payload[0] + + def set_play_mode(self, mode): + + self._send_command( + 0x0B, + bytes([ + 0x01, + mode + ]) + ) + + time.sleep(0.1) + + # --------------------------------------------------------- + # Repeat + # --------------------------------------------------------- + + def repeat_between( + self, + start_min, + start_sec, + end_min, + end_sec + ): + + self._send_command( + 0x08, + bytes([ + 0x00, + start_min, + start_sec, + end_min, + end_sec + ]) + ) + + time.sleep(0.1) + + def stop_repeat(self): + + self._send_command( + 0x08, + bytes([0x01]) + ) + + time.sleep(0.1) + + # --------------------------------------------------------- + # Sleep + # --------------------------------------------------------- + + def sleep(self): + frame = bytes([ + 0x0D, + 0xF3, + 0x01, + 0x01, + 0x02 + ]) + + self.uart.write(frame) + diff --git a/sd/placeholder.txt b/sd/placeholder.txt new file mode 100644 index 0000000..de7fc0d --- /dev/null +++ b/sd/placeholder.txt @@ -0,0 +1 @@ +SD cards mounted at /sd will hide this file from Python. diff --git a/settings.toml b/settings.toml new file mode 100644 index 0000000..e69de29