172 lines
5.5 KiB
Python
172 lines
5.5 KiB
Python
# SPDX-License-Identifier: MIT
|
|
# Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
|
|
|
import aiorepl # type: ignore
|
|
import asyncio
|
|
from errno import ENOENT
|
|
import machine
|
|
import micropython
|
|
import network
|
|
import os
|
|
import time
|
|
import ubinascii
|
|
import sys
|
|
|
|
# Own modules
|
|
import app
|
|
from audiocore import AudioContext
|
|
import frozen_frontend # noqa: F401
|
|
from mfrc522 import MFRC522
|
|
from mp3player import MP3Player
|
|
from nfc import Nfc
|
|
from rp2_neopixel import NeoPixel
|
|
from utils import BTreeFileManager, Buttons, SDContext, TimerManager, LedManager, Configuration
|
|
from webserver import start_webserver
|
|
|
|
try:
|
|
import hwconfig
|
|
except ImportError:
|
|
print("Fatal: No hwconfig.py found")
|
|
raise
|
|
|
|
micropython.alloc_emergency_exception_buf(100)
|
|
|
|
# Machine setup
|
|
hwconfig.board_init()
|
|
|
|
# high prio for proc 1
|
|
machine.mem32[0x40030000 + 0x00] = 0x10
|
|
|
|
|
|
def setup_wifi(ssid='', passphrase='', sec=network.WLAN.SEC_WPA2_WPA3):
|
|
network.hostname("TonberryPico")
|
|
if ssid is None or ssid == '':
|
|
apname = f"TonberryPicoAP_{machine.unique_id().hex()}"
|
|
print(f"Create AP {apname}")
|
|
wlan = network.WLAN(network.WLAN.IF_AP)
|
|
wlan.config(ssid=apname, security=wlan.SEC_OPEN)
|
|
wlan.active(True)
|
|
else:
|
|
print(f"Connect to SSID {ssid} with passphrase {passphrase}...")
|
|
wlan = network.WLAN()
|
|
wlan.active(True)
|
|
wlan.connect(ssid, passphrase if passphrase is not None else '', security=sec)
|
|
|
|
# configure power management
|
|
wlan.config(pm=network.WLAN.PM_PERFORMANCE)
|
|
|
|
mac = ubinascii.hexlify(network.WLAN().config('mac'), ':').decode()
|
|
print(f" mac: {mac}")
|
|
print(f" channel: {wlan.config('channel')}")
|
|
print(f" essid: {wlan.config('essid')}")
|
|
print(f" txpower: {wlan.config('txpower')}")
|
|
print(f"ifconfig: {wlan.ifconfig()}")
|
|
|
|
|
|
async def wdt_task(wdt):
|
|
# TODO: more checking of app health
|
|
# Right now this only protects against the asyncio executor crashing completely
|
|
while True:
|
|
await asyncio.sleep_ms(100)
|
|
wdt.feed()
|
|
|
|
DB_PATH = '/sd/tonberry.db'
|
|
|
|
config = Configuration()
|
|
|
|
# Setup LEDs
|
|
np = NeoPixel(hwconfig.LED_DIN, config.get_led_count(), sm=1)
|
|
led_max = config.get_led_max()
|
|
np.fill((led_max, led_max, 0))
|
|
np.write()
|
|
|
|
|
|
def run():
|
|
asyncio.new_event_loop()
|
|
|
|
if machine.Pin(hwconfig.BUTTONS[1], machine.Pin.IN, machine.Pin.PULL_UP).value() == 0:
|
|
np.fill((0, 0, led_max))
|
|
np.write()
|
|
# Force default access point
|
|
setup_wifi('', '')
|
|
else:
|
|
setup_wifi(config.get_wifi_ssid(), config.get_wifi_passphrase())
|
|
|
|
# Setup MP3 player
|
|
with SDContext(mosi=hwconfig.SD_DI, miso=hwconfig.SD_DO, sck=hwconfig.SD_SCK, ss=hwconfig.SD_CS,
|
|
baudrate=hwconfig.SD_CLOCKRATE):
|
|
# Temporary hack: build database from folders if no database exists
|
|
# Can be removed once playlists can be created via API
|
|
try:
|
|
_ = os.stat(DB_PATH)
|
|
except OSError as ex:
|
|
if ex.errno == ENOENT:
|
|
print("No playlist DB found, trying to build DB from tag dirs")
|
|
builddb()
|
|
|
|
with BTreeFileManager(DB_PATH) as playlistdb, \
|
|
AudioContext(hwconfig.I2S_DIN, hwconfig.I2S_DCLK, hwconfig.I2S_LRCLK) as audioctx:
|
|
|
|
# Setup NFC
|
|
reader = MFRC522(spi_id=hwconfig.RC522_SPIID, sck=hwconfig.RC522_SCK, miso=hwconfig.RC522_MISO,
|
|
mosi=hwconfig.RC522_MOSI, cs=hwconfig.RC522_SS, rst=hwconfig.RC522_RST, tocard_retries=20)
|
|
|
|
# Setup app
|
|
deps = app.Dependencies(mp3player=lambda the_app: MP3Player(audioctx, the_app),
|
|
nfcreader=lambda the_app: Nfc(reader, the_app),
|
|
buttons=lambda the_app: Buttons(the_app, config, hwconfig),
|
|
playlistdb=lambda _: playlistdb,
|
|
hwconfig=lambda _: hwconfig,
|
|
leds=lambda _: LedManager(np, config),
|
|
config=lambda _: config)
|
|
the_app = app.PlayerApp(deps)
|
|
|
|
start_webserver(config, the_app)
|
|
# Start
|
|
wdt = machine.WDT(timeout=2000)
|
|
asyncio.create_task(aiorepl.task({'timer_manager': TimerManager(),
|
|
'app': the_app}))
|
|
asyncio.create_task(wdt_task(wdt))
|
|
asyncio.get_event_loop().run_forever()
|
|
|
|
|
|
def builddb():
|
|
"""
|
|
For testing, build a playlist db based on the previous tag directory format.
|
|
Can be removed once uploading files / playlist via the web api is possible.
|
|
"""
|
|
try:
|
|
os.unlink(DB_PATH)
|
|
except OSError:
|
|
pass
|
|
with BTreeFileManager(DB_PATH) as db:
|
|
for name, type_, _, _ in os.ilistdir(b'/sd'):
|
|
if type_ != 0x4000:
|
|
continue
|
|
fl = [b'/sd/' + name + b'/' + x for x in os.listdir(b'/sd/' + name) if x.endswith(b'.mp3')]
|
|
db.createPlaylistForTag(name, fl)
|
|
os.sync()
|
|
|
|
|
|
def error_blink():
|
|
while True:
|
|
np.fill((led_max, 0, 0))
|
|
np.write()
|
|
time.sleep_ms(500)
|
|
np.fill((0, 0, 0))
|
|
np.write()
|
|
time.sleep_ms(500)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
time.sleep(1)
|
|
if machine.Pin(hwconfig.BUTTONS[0], machine.Pin.IN, machine.Pin.PULL_UP).value() != 0:
|
|
try:
|
|
run()
|
|
except Exception as ex:
|
|
sys.print_exception(ex)
|
|
error_blink()
|
|
else:
|
|
np.fill((led_max, 0, 0))
|
|
np.write()
|