host.controller_web_editor.routes.config
JSON <-> YAML round-trip for FullConfig.
The frontend never parses YAML. Server-side, we hand off to
utils.controller.config_io so loading, saving, default-omission,
and legacy-format migration stay in one place.
1"""JSON <-> YAML round-trip for FullConfig. 2 3The frontend never parses YAML. Server-side, we hand off to 4:mod:`utils.controller.config_io` so loading, saving, default-omission, 5and legacy-format migration stay in one place. 6""" 7 8from __future__ import annotations 9 10from dataclasses import asdict 11from pathlib import Path 12 13from utils.controller.config_io import ( 14 CONFIG_VERSION, 15 _dict_to_action, 16 _dict_to_controller, 17 load_config, 18 save_config, 19) 20from utils.controller.model import ( 21 ActionDefinition, 22 ControllerConfig, 23 EventTriggerMode, 24 FullConfig, 25 InputType, 26) 27 28 29def config_to_json(config: FullConfig) -> dict: 30 """Serialize FullConfig to JSON-safe primitives. 31 32 Enums become their string values; sets become sorted lists. 33 """ 34 actions = {} 35 for qname, action in config.actions.items(): 36 d = asdict(action) 37 d["input_type"] = action.input_type.value 38 d["trigger_mode"] = action.trigger_mode.value 39 actions[qname] = d 40 41 controllers = {} 42 for port, ctrl in config.controllers.items(): 43 controllers[str(port)] = { 44 "port": ctrl.port, 45 "name": ctrl.name, 46 "controller_type": ctrl.controller_type, 47 "bindings": {k: list(v) for k, v in ctrl.bindings.items()}, 48 } 49 50 return { 51 "version": config.version or CONFIG_VERSION, 52 "actions": actions, 53 "controllers": controllers, 54 "empty_groups": sorted(config.empty_groups), 55 } 56 57 58def config_from_json(payload: dict) -> FullConfig: 59 """Inverse of :func:`config_to_json`.""" 60 actions: dict[str, ActionDefinition] = {} 61 for qname, raw in (payload.get("actions") or {}).items(): 62 group = raw.get("group", "general") 63 name = raw.get("name") or qname.split(".", 1)[-1] 64 action = _dict_to_action(name, raw, group=group) 65 actions[action.qualified_name] = action 66 67 controllers: dict[int, ControllerConfig] = {} 68 for port_str, raw in (payload.get("controllers") or {}).items(): 69 port = int(port_str) 70 controllers[port] = _dict_to_controller(port, raw) 71 72 return FullConfig( 73 actions=actions, 74 controllers=controllers, 75 empty_groups=set(payload.get("empty_groups") or []), 76 version=payload.get("version") or CONFIG_VERSION, 77 ) 78 79 80def load_as_json(path: Path) -> dict: 81 """Read a YAML config file and return JSON-safe primitives.""" 82 return config_to_json(load_config(path)) 83 84 85def save_from_json(path: Path, payload: dict) -> None: 86 """Write a JSON payload back to a YAML config file.""" 87 save_config(config_from_json(payload), path) 88 89 90# Re-exported for callers that want to inspect enum vocabularies. 91__all__ = [ 92 "EventTriggerMode", 93 "InputType", 94 "config_from_json", 95 "config_to_json", 96 "load_as_json", 97 "save_from_json", 98]
43class EventTriggerMode(Enum): 44 """How a button/analog action is triggered or shaped. 45 46 Button modes control when commands fire via ``commands2.button.Trigger`` 47 bindings (not to be confused with the Xbox "trigger" — the analog 48 left_trigger / right_trigger inputs). Each mode maps to a Trigger 49 binding method: ON_TRUE -> ``.onTrue()``, WHILE_TRUE -> ``.whileTrue()``, 50 etc. 51 52 Analog modes control how the analog value is shaped/curved before 53 it reaches the subsystem. 54 """ 55 # Button modes 56 ON_TRUE = "on_true" 57 ON_FALSE = "on_false" 58 WHILE_TRUE = "while_true" 59 WHILE_FALSE = "while_false" 60 TOGGLE_ON_TRUE = "toggle_on_true" 61 # Analog response curves 62 RAW = "raw" 63 SCALED = "scaled" 64 SQUARED = "squared" 65 SEGMENTED = "segmented" 66 SPLINE = "spline"
How a button/analog action is triggered or shaped.
Button modes control when commands fire via commands2.button.Trigger
bindings (not to be confused with the Xbox "trigger" — the analog
left_trigger / right_trigger inputs). Each mode maps to a Trigger
binding method: ON_TRUE -> .onTrue(), WHILE_TRUE -> .whileTrue(),
etc.
Analog modes control how the analog value is shaped/curved before it reaches the subsystem.
86class InputType(Enum): 87 """Type of controller input an action is designed for. 88 89 BOOLEAN_TRIGGER converts an analog axis (e.g. left_trigger) into a 90 boolean using a threshold comparison — not related to 91 ``commands2.button.Trigger``. 92 """ 93 BUTTON = "button" 94 ANALOG = "analog" 95 OUTPUT = "output" 96 BOOLEAN_TRIGGER = "boolean_trigger" 97 VIRTUAL_ANALOG = "virtual_analog"
Type of controller input an action is designed for.
BOOLEAN_TRIGGER converts an analog axis (e.g. left_trigger) into a
boolean using a threshold comparison — not related to
commands2.button.Trigger.
59def config_from_json(payload: dict) -> FullConfig: 60 """Inverse of :func:`config_to_json`.""" 61 actions: dict[str, ActionDefinition] = {} 62 for qname, raw in (payload.get("actions") or {}).items(): 63 group = raw.get("group", "general") 64 name = raw.get("name") or qname.split(".", 1)[-1] 65 action = _dict_to_action(name, raw, group=group) 66 actions[action.qualified_name] = action 67 68 controllers: dict[int, ControllerConfig] = {} 69 for port_str, raw in (payload.get("controllers") or {}).items(): 70 port = int(port_str) 71 controllers[port] = _dict_to_controller(port, raw) 72 73 return FullConfig( 74 actions=actions, 75 controllers=controllers, 76 empty_groups=set(payload.get("empty_groups") or []), 77 version=payload.get("version") or CONFIG_VERSION, 78 )
Inverse of config_to_json().
30def config_to_json(config: FullConfig) -> dict: 31 """Serialize FullConfig to JSON-safe primitives. 32 33 Enums become their string values; sets become sorted lists. 34 """ 35 actions = {} 36 for qname, action in config.actions.items(): 37 d = asdict(action) 38 d["input_type"] = action.input_type.value 39 d["trigger_mode"] = action.trigger_mode.value 40 actions[qname] = d 41 42 controllers = {} 43 for port, ctrl in config.controllers.items(): 44 controllers[str(port)] = { 45 "port": ctrl.port, 46 "name": ctrl.name, 47 "controller_type": ctrl.controller_type, 48 "bindings": {k: list(v) for k, v in ctrl.bindings.items()}, 49 } 50 51 return { 52 "version": config.version or CONFIG_VERSION, 53 "actions": actions, 54 "controllers": controllers, 55 "empty_groups": sorted(config.empty_groups), 56 }
Serialize FullConfig to JSON-safe primitives.
Enums become their string values; sets become sorted lists.
81def load_as_json(path: Path) -> dict: 82 """Read a YAML config file and return JSON-safe primitives.""" 83 return config_to_json(load_config(path))
Read a YAML config file and return JSON-safe primitives.
86def save_from_json(path: Path, payload: dict) -> None: 87 """Write a JSON payload back to a YAML config file.""" 88 save_config(config_from_json(payload), path)
Write a JSON payload back to a YAML config file.