Update config.py

Added a new ButtonConfig dataclass:

enabled: bool = False

pin: int = 17 (BCM pin for the button)

long_press_seconds: float = 1.0

Extended the main Config dataclass with a button: ButtonConfig field.

Updated load_config_from_json() to construct button_config from the "button" section in config.json.

This allows enabling/disabling the button, changing the GPIO pin, and tuning the long-press threshold (and optional poll interval) without code changes.
This commit is contained in:
imonlinux
2025-11-29 23:40:16 -08:00
committed by GitHub
parent 93bdb60fc0
commit edc66183c3
+29 -8
View File
@@ -5,6 +5,9 @@ from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Dict, List, Optional
import logging
_LOGGER = logging.getLogger(__name__)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Configuration Dataclasses # Configuration Dataclasses
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -16,6 +19,7 @@ class AudioConfig:
input_block_size: int = 1024 input_block_size: int = 1024
output_device: Optional[str] = None output_device: Optional[str] = None
@dataclass @dataclass
class WakeWordConfig: class WakeWordConfig:
"""Settings for wake word detection.""" """Settings for wake word detection."""
@@ -25,12 +29,14 @@ class WakeWordConfig:
refractory_seconds: float = 2.0 refractory_seconds: float = 2.0
download_dir: str = "local" download_dir: str = "local"
@dataclass @dataclass
class ESPHomeConfig: class ESPHomeConfig:
"""Settings for the ESPHome API server.""" """Settings for the ESPHome API server."""
host: str = "0.0.0.0" host: str = "0.0.0.0"
port: int = 6053 port: int = 6053
@dataclass @dataclass
class LedConfig: class LedConfig:
"""Settings for the LED strip.""" """Settings for the LED strip."""
@@ -38,7 +44,8 @@ class LedConfig:
interface: str = "spi" interface: str = "spi"
clock_pin: int = 13 clock_pin: int = 13
data_pin: int = 12 data_pin: int = 12
num_leds: int = 3 # Note: This will be overridden by 'preferences.json' if it exists num_leds: int = 3 # Note: overridden by 'preferences.json' if it exists
@dataclass @dataclass
class MqttConfig: class MqttConfig:
@@ -49,6 +56,17 @@ class MqttConfig:
username: Optional[str] = None username: Optional[str] = None
password: Optional[str] = None password: Optional[str] = None
@dataclass
class ButtonConfig:
"""Settings for a hardware momentary button (e.g. ReSpeaker 2-Mic HAT)."""
enabled: bool = False
# BCM GPIO pin number for the button input.
pin: int = 17
# Press duration (in seconds) to be considered a "long press".
long_press_seconds: float = 1.0
@dataclass @dataclass
class AppConfig: class AppConfig:
"""General application settings.""" """General application settings."""
@@ -58,6 +76,7 @@ class AppConfig:
preferences_file: str = "preferences.json" preferences_file: str = "preferences.json"
debug: bool = False debug: bool = False
@dataclass @dataclass
class Config: class Config:
"""Main configuration object.""" """Main configuration object."""
@@ -67,6 +86,7 @@ class Config:
esphome: ESPHomeConfig = field(default_factory=ESPHomeConfig) esphome: ESPHomeConfig = field(default_factory=ESPHomeConfig)
led: LedConfig = field(default_factory=LedConfig) led: LedConfig = field(default_factory=LedConfig)
mqtt: MqttConfig = field(default_factory=MqttConfig) mqtt: MqttConfig = field(default_factory=MqttConfig)
button: ButtonConfig = field(default_factory=ButtonConfig)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Helper Function # Helper Function
@@ -74,7 +94,7 @@ class Config:
def load_config_from_json(config_path: Path) -> Config: def load_config_from_json(config_path: Path) -> Config:
"""Loads configuration from a JSON file and populates dataclasses.""" """Loads configuration from a JSON file and populates dataclasses."""
# --- Step 1: Load raw JSON data --- # --- Step 1: Load raw JSON data ---
try: try:
with open(config_path, "r", encoding="utf-8") as f: with open(config_path, "r", encoding="utf-8") as f:
@@ -88,15 +108,18 @@ def load_config_from_json(config_path: Path) -> Config:
# --- Step 2: Create config objects from raw data --- # --- Step 2: Create config objects from raw data ---
if "app" not in raw_data: if "app" not in raw_data:
raise ValueError("Configuration file must contain an 'app' section with a 'name'.") raise ValueError(
"Configuration file must contain an 'app' section with a 'name'."
)
app_config = AppConfig(**raw_data.get("app", {})) app_config = AppConfig(**raw_data.get("app", {}))
audio_config = AudioConfig(**raw_data.get("audio", {})) audio_config = AudioConfig(**raw_data.get("audio", {}))
wake_word_config = WakeWordConfig(**raw_data.get("wake_word", {})) wake_word_config = WakeWordConfig(**raw_data.get("wake_word", {}))
esphome_config = ESPHomeConfig(**raw_data.get("esphome", {})) esphome_config = ESPHomeConfig(**raw_data.get("esphome", {}))
led_config = LedConfig(**raw_data.get("led", {})) led_config = LedConfig(**raw_data.get("led", {}))
mqtt_config = MqttConfig(**raw_data.get("mqtt", {})) mqtt_config = MqttConfig(**raw_data.get("mqtt", {}))
button_config = ButtonConfig(**raw_data.get("button", {}))
# --- Step 3: Set MQTT 'enabled' flag --- # --- Step 3: Set MQTT 'enabled' flag ---
if mqtt_config.host: if mqtt_config.host:
mqtt_config.enabled = True mqtt_config.enabled = True
@@ -109,7 +132,5 @@ def load_config_from_json(config_path: Path) -> Config:
esphome=esphome_config, esphome=esphome_config,
led=led_config, led=led_config,
mqtt=mqtt_config, mqtt=mqtt_config,
button=button_config,
) )
import logging
_LOGGER = logging.getLogger(__name__)