feat: Make button mapping configurable

- Remove Pin to button mapping from hwconfig, replace with a simple list
  of Pins to which buttons are attached
- Add BUTTON_MAP to config.json which maps indices in the HW button list
  to button names
- Update utils.Buttons to use the button map to connect the correct pins
  to the matching key codes

Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
2025-11-30 12:31:50 +01:00
parent 83deb1b4c2
commit 856bf34161
5 changed files with 60 additions and 22 deletions

View File

@@ -30,10 +30,12 @@ RC522_SS = Pin.board.GP13
LED_DIN = Pin.board.GP16
# Buttons
BUTTON_VOLUP = Pin.board.GP17
BUTTON_VOLDOWN = Pin.board.GP19
BUTTON_NEXT = Pin.board.GP18
BUTTON_POWER = Pin.board.GP21
BUTTONS = [Pin.board.GP17,
Pin.board.GP18,
Pin.board.GP19,
Pin.board.GP20,
Pin.board.GP21,
]
# Power
POWER_EN = Pin.board.GP22

View File

@@ -29,10 +29,10 @@ RC522_SS = Pin.board.GP13
LED_DIN = Pin.board.GP16
# Buttons
BUTTON_VOLUP = Pin.board.GP17
BUTTON_VOLDOWN = Pin.board.GP19
BUTTON_NEXT = Pin.board.GP18
BUTTON_POWER = None
BUTTONS = [Pin.board.GP17,
Pin.board.GP18,
Pin.board.GP19,
]
# Power
POWER_EN = None

View File

@@ -89,9 +89,7 @@ def run():
# 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, pin_volup=hwconfig.BUTTON_VOLUP,
pin_voldown=hwconfig.BUTTON_VOLDOWN,
pin_next=hwconfig.BUTTON_NEXT),
buttons=lambda the_app: Buttons(the_app, config, hwconfig),
playlistdb=lambda _: playlistdb,
hwconfig=lambda _: hwconfig,
leds=lambda _: LedManager(np),
@@ -124,5 +122,5 @@ def builddb():
if __name__ == '__main__':
time.sleep(1)
if machine.Pin(hwconfig.BUTTON_VOLUP, machine.Pin.IN, machine.Pin.PULL_UP).value() != 0:
if machine.Pin(hwconfig.BUTTONS[0], machine.Pin.IN, machine.Pin.PULL_UP).value() != 0:
run()

View File

@@ -17,14 +17,27 @@ if TYPE_CHECKING:
class Buttons:
def __init__(self, cb: "ButtonCallback", pin_volup=17, pin_voldown=19, pin_next=18):
self.VOLUP = micropython.const(1)
self.VOLDOWN = micropython.const(2)
self.NEXT = micropython.const(3)
VOLUP = micropython.const(1)
VOLDOWN = micropython.const(2)
NEXT = micropython.const(3)
PREV = micropython.const(4)
PLAY_PAUSE = micropython.const(5)
KEYMAP = {VOLUP: 'VOLUP',
VOLDOWN: 'VOLDOWN',
NEXT: 'NEXT',
PREV: 'PREV',
PLAY_PAUSE: 'PLAY_PAUSE'}
def __init__(self, cb: "ButtonCallback", config, hwconfig):
self.button_map = config.get_button_map()
self.hw_buttons = hwconfig.BUTTONS
self.cb = cb
self.buttons = {machine.Pin(pin_volup, machine.Pin.IN, machine.Pin.PULL_UP): self.VOLUP,
machine.Pin(pin_voldown, machine.Pin.IN, machine.Pin.PULL_UP): self.VOLDOWN,
machine.Pin(pin_next, machine.Pin.IN, machine.Pin.PULL_UP): self.NEXT}
self.buttons = dict()
for key_id, key_name in self.KEYMAP.items():
pin = self._get_pin(key_name)
if pin is None:
continue
self.buttons[pin] = key_id
self.int_flag = asyncio.ThreadSafeFlag()
self.pressed: list[int] = []
self.last: dict[int, int] = {}
@@ -32,6 +45,17 @@ class Buttons:
button.irq(handler=self._interrupt, trigger=machine.Pin.IRQ_FALLING | machine.Pin.IRQ_RISING)
asyncio.create_task(self.task())
def _get_pin(self, key):
key_id = self.button_map.get(key, None)
if key_id is None:
return None
if key_id < 0 or key_id >= len(self.hw_buttons):
return None
pin = self.hw_buttons[key_id]
if pin is not None:
pin.init(machine.Pin.IN, machine.Pin.PULL_UP)
return pin
def _interrupt(self, button):
keycode = self.buttons[button]
last = self.last.get(keycode, 0)

View File

@@ -4,6 +4,10 @@
from errno import ENOENT
import json
import os
try:
from typing import TYPE_CHECKING, Mapping
except ImportError:
TYPE_CHECKING = False
class Configuration:
@@ -11,6 +15,13 @@ class Configuration:
'LED_COUNT': 1,
'IDLE_TIMEOUT_SECS': 60,
'TAG_TIMEOUT_SECS': 5,
'BUTTON_MAP': {
'PLAY_PAUSE': 4,
'VOLUP': 0,
'VOLDOWN': 2,
'PREV': None,
'NEXT': 1,
}
}
def __init__(self, config_path='/config.json'):
@@ -49,11 +60,14 @@ class Configuration:
def _get(self, key):
return self.config.get(key, self.DEFAULT_CONFIG[key])
def get_led_count(self):
def get_led_count(self) -> int:
return self._get('LED_COUNT')
def get_idle_timeout(self):
def get_idle_timeout(self) -> int:
return self._get('IDLE_TIMEOUT_SECS')
def get_tag_timeout(self):
def get_tag_timeout(self) -> int:
return self._get('TAG_TIMEOUT_SECS')
def get_button_map(self) -> Mapping[str, int | None]:
return self._get('BUTTON_MAP')