diff --git a/pyproject.toml b/pyproject.toml index 2614a8a..4900f0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "pyfirmata2>=2.5.0", "pyzmq>=26.2.0", "thorlabs-apt-device>=0.3.8", + "tomli-w>=1.0.0", "typer>=0.15.1", ] diff --git a/src/pqnstack/app/api/main.py b/src/pqnstack/app/api/main.py index 175e089..34ee765 100644 --- a/src/pqnstack/app/api/main.py +++ b/src/pqnstack/app/api/main.py @@ -3,6 +3,7 @@ from pqnstack.app.api.routes import chsh from pqnstack.app.api.routes import coordination from pqnstack.app.api.routes import debug +from pqnstack.app.api.routes import games from pqnstack.app.api.routes import qkd from pqnstack.app.api.routes import rng from pqnstack.app.api.routes import serial @@ -16,3 +17,4 @@ api_router.include_router(serial.router) api_router.include_router(coordination.router) api_router.include_router(debug.router) +api_router.include_router(games.router) diff --git a/src/pqnstack/app/api/routes/games.py b/src/pqnstack/app/api/routes/games.py new file mode 100644 index 0000000..99f0322 --- /dev/null +++ b/src/pqnstack/app/api/routes/games.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from pqnstack.app.core.config import GamesAvailability +from pqnstack.app.core.config import get_settings + +router = APIRouter(prefix="/games", tags=["games"]) + + +@router.get("/availability") +def get_availability() -> GamesAvailability: + return get_settings().games_availability diff --git a/src/pqnstack/app/core/config.py b/src/pqnstack/app/core/config.py index 0505b17..412bc19 100644 --- a/src/pqnstack/app/core/config.py +++ b/src/pqnstack/app/core/config.py @@ -43,6 +43,12 @@ class QKDSettings(BaseModel): measurement_config: MeasurementConfig = Field(default_factory=lambda: MeasurementConfig(integration_time_s=5)) +class GamesAvailability(BaseModel): + chsh: bool = True # "Verify Quantum Link" + qf: bool = True # "Quantum Fortune" + ssm: bool = True # "Share a Secret Message" + + class Settings(BaseSettings): node_name: str = "node1" router_name: str = "router1" @@ -54,6 +60,7 @@ class Settings(BaseSettings): timetagger: tuple[str, str] | None = None # Name of the timetagger to use for the CHSH experiment. rotary_encoder_address: str = "/dev/ttyACM0" virtual_rotator: bool = False # If True, use terminal input instead of hardware rotary encoder + games_availability: GamesAvailability = Field(default_factory=GamesAvailability) model_config = SettingsConfigDict( toml_file="./config.toml", diff --git a/src/pqnstack/cli.py b/src/pqnstack/cli.py index d2d7222..988a162 100644 --- a/src/pqnstack/cli.py +++ b/src/pqnstack/cli.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Annotated +import tomli_w import typer from pqnstack.base.errors import InvalidNetworkConfigurationError @@ -184,5 +185,37 @@ def start_router( router.start() +@app.command() +def toggle_game( + games: Annotated[list[str], typer.Argument(help="Games to toggle: chsh, qf, ssm")], + enable: Annotated[bool, typer.Option("--enable/--disable", help="Enable or disable the games")] = True, # noqa: FBT002 + config: Annotated[str, typer.Option(help="Path to config.toml")] = "./config.toml", +) -> None: + """ + Enable or disable one or more games in config.toml. + + Changes take effect on the next server restart. Games: chsh (Verify Quantum Link), qf (Quantum Fortune), ssm (Share a Secret Message). + """ + valid_games = {"chsh", "qf", "ssm"} + invalid = [g for g in games if g not in valid_games] + if invalid: + msg = f"Game(s) must be one of: chsh, qf, ssm. Invalid: {invalid}" + raise InvalidNetworkConfigurationError(msg) + + path = Path(config) + with path.open("rb") as f: + cfg = tomllib.load(f) + + cfg.setdefault("games_availability", {}) + for game in games: + cfg["games_availability"][game] = enable + + with path.open("wb") as f: + tomli_w.dump(cfg, f) + + status = "enabled" if enable else "disabled" + logger.info("Games %s %s in %s. Restart the server for changes to take effect.", games, status, path) + + if __name__ == "__main__": app() diff --git a/uv.lock b/uv.lock index 403bd66..8f7b208 100644 --- a/uv.lock +++ b/uv.lock @@ -733,6 +733,7 @@ dependencies = [ { name = "pyfirmata2" }, { name = "pyzmq" }, { name = "thorlabs-apt-device" }, + { name = "tomli-w" }, { name = "typer" }, ] @@ -761,6 +762,7 @@ requires-dist = [ { name = "pyfirmata2", specifier = ">=2.5.0" }, { name = "pyzmq", specifier = ">=26.2.0" }, { name = "thorlabs-apt-device", specifier = ">=0.3.8" }, + { name = "tomli-w", specifier = ">=1.0.0" }, { name = "typer", specifier = ">=0.15.1" }, ] provides-extras = ["webapp"] @@ -1230,6 +1232,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/38/60afff0deb2379c5feae251a5da9e9773c6ac8e2f09fbdac8d2980e28aa2/thorlabs_apt_device-0.3.8-py3-none-any.whl", hash = "sha256:84897581dc99e6f1ab774f07f9466d9137c94ac3dfa0fa59cdd305a4c9682872", size = 64631, upload-time = "2024-03-09T09:39:16.829Z" }, ] +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + [[package]] name = "typer" version = "0.20.0"