Add demo mode: virtual hardware sandbox for testing without real devices
Demo mode provides a complete sandbox environment with: - Virtual capture engine (radial rainbow test pattern on 3 displays) - Virtual audio engine (synthetic music-like audio on 2 devices) - Virtual LED device provider (strip/60, matrix/256, ring/24 LEDs) - Isolated data directory (data/demo/) with auto-seeded sample entities - Dedicated config (config/demo_config.yaml) with pre-configured API key - Frontend indicator (DEMO badge + dismissible banner) - Engine filtering (only demo engines visible in demo mode) - Separate entry point: python -m wled_controller.demo (port 8081) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,12 +73,22 @@ class Config(BaseSettings):
|
||||
case_sensitive=False,
|
||||
)
|
||||
|
||||
demo: bool = False
|
||||
|
||||
server: ServerConfig = Field(default_factory=ServerConfig)
|
||||
auth: AuthConfig = Field(default_factory=AuthConfig)
|
||||
storage: StorageConfig = Field(default_factory=StorageConfig)
|
||||
mqtt: MQTTConfig = Field(default_factory=MQTTConfig)
|
||||
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
||||
|
||||
def model_post_init(self, __context: object) -> None:
|
||||
"""Override storage paths when demo mode is active."""
|
||||
if self.demo:
|
||||
for field_name in self.storage.model_fields:
|
||||
value = getattr(self.storage, field_name)
|
||||
if isinstance(value, str) and value.startswith("data/"):
|
||||
setattr(self.storage, field_name, value.replace("data/", "data/demo/", 1))
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, config_path: str | Path) -> "Config":
|
||||
"""Load configuration from YAML file.
|
||||
@@ -104,8 +114,9 @@ class Config(BaseSettings):
|
||||
|
||||
Tries to load from:
|
||||
1. Environment variable WLED_CONFIG_PATH
|
||||
2. ./config/default_config.yaml
|
||||
3. Default values
|
||||
2. WLED_DEMO=true → ./config/demo_config.yaml (if it exists)
|
||||
3. ./config/default_config.yaml
|
||||
4. Default values
|
||||
|
||||
Returns:
|
||||
Config instance
|
||||
@@ -115,6 +126,12 @@ class Config(BaseSettings):
|
||||
if config_path:
|
||||
return cls.from_yaml(config_path)
|
||||
|
||||
# Demo mode: try dedicated demo config first
|
||||
if os.getenv("WLED_DEMO", "").lower() in ("true", "1", "yes"):
|
||||
demo_path = Path("config/demo_config.yaml")
|
||||
if demo_path.exists():
|
||||
return cls.from_yaml(demo_path)
|
||||
|
||||
# Try default location
|
||||
default_path = Path("config/default_config.yaml")
|
||||
if default_path.exists():
|
||||
@@ -149,3 +166,8 @@ def reload_config() -> Config:
|
||||
global config
|
||||
config = Config.load()
|
||||
return config
|
||||
|
||||
|
||||
def is_demo_mode() -> bool:
|
||||
"""Check whether the application is running in demo mode."""
|
||||
return get_config().demo
|
||||
|
||||
Reference in New Issue
Block a user