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 LED_DIN = Pin.board.GP16
# Buttons # Buttons
BUTTON_VOLUP = Pin.board.GP17 BUTTONS = [Pin.board.GP17,
BUTTON_VOLDOWN = Pin.board.GP19 Pin.board.GP18,
BUTTON_NEXT = Pin.board.GP18 Pin.board.GP19,
BUTTON_POWER = Pin.board.GP21 Pin.board.GP20,
Pin.board.GP21,
]
# Power # Power
POWER_EN = Pin.board.GP22 POWER_EN = Pin.board.GP22

View File

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

View File

@@ -89,9 +89,7 @@ def run():
# Setup app # Setup app
deps = app.Dependencies(mp3player=lambda the_app: MP3Player(audioctx, the_app), deps = app.Dependencies(mp3player=lambda the_app: MP3Player(audioctx, the_app),
nfcreader=lambda the_app: Nfc(reader, the_app), nfcreader=lambda the_app: Nfc(reader, the_app),
buttons=lambda the_app: Buttons(the_app, pin_volup=hwconfig.BUTTON_VOLUP, buttons=lambda the_app: Buttons(the_app, config, hwconfig),
pin_voldown=hwconfig.BUTTON_VOLDOWN,
pin_next=hwconfig.BUTTON_NEXT),
playlistdb=lambda _: playlistdb, playlistdb=lambda _: playlistdb,
hwconfig=lambda _: hwconfig, hwconfig=lambda _: hwconfig,
leds=lambda _: LedManager(np), leds=lambda _: LedManager(np),
@@ -124,5 +122,5 @@ def builddb():
if __name__ == '__main__': if __name__ == '__main__':
time.sleep(1) 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() run()

View File

@@ -17,14 +17,27 @@ if TYPE_CHECKING:
class Buttons: class Buttons:
def __init__(self, cb: "ButtonCallback", pin_volup=17, pin_voldown=19, pin_next=18): VOLUP = micropython.const(1)
self.VOLUP = micropython.const(1) VOLDOWN = micropython.const(2)
self.VOLDOWN = micropython.const(2) NEXT = micropython.const(3)
self.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.cb = cb
self.buttons = {machine.Pin(pin_volup, machine.Pin.IN, machine.Pin.PULL_UP): self.VOLUP, self.buttons = dict()
machine.Pin(pin_voldown, machine.Pin.IN, machine.Pin.PULL_UP): self.VOLDOWN, for key_id, key_name in self.KEYMAP.items():
machine.Pin(pin_next, machine.Pin.IN, machine.Pin.PULL_UP): self.NEXT} pin = self._get_pin(key_name)
if pin is None:
continue
self.buttons[pin] = key_id
self.int_flag = asyncio.ThreadSafeFlag() self.int_flag = asyncio.ThreadSafeFlag()
self.pressed: list[int] = [] self.pressed: list[int] = []
self.last: dict[int, 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) button.irq(handler=self._interrupt, trigger=machine.Pin.IRQ_FALLING | machine.Pin.IRQ_RISING)
asyncio.create_task(self.task()) 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): def _interrupt(self, button):
keycode = self.buttons[button] keycode = self.buttons[button]
last = self.last.get(keycode, 0) last = self.last.get(keycode, 0)

View File

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