Soinua jotzen du
This commit is contained in:
3
boot_out.txt
Normal file
3
boot_out.txt
Normal file
@@ -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
|
||||||
21
code.py
Normal file
21
code.py
Normal file
@@ -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)
|
||||||
|
|
||||||
522
lib/audioplayer.py
Normal file
522
lib/audioplayer.py
Normal file
@@ -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)
|
||||||
|
|
||||||
1
sd/placeholder.txt
Normal file
1
sd/placeholder.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SD cards mounted at /sd will hide this file from Python.
|
||||||
0
settings.toml
Normal file
0
settings.toml
Normal file
Reference in New Issue
Block a user