-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
146 lines (118 loc) · 4.65 KB
/
Copy pathconfig.py
File metadata and controls
146 lines (118 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""
Configuration management for the G1 Control API.
Loads settings from config.yaml if present, environment variables,
or falls back to sensible defaults.
"""
import os
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
logger = logging.getLogger(__name__)
CONFIG_PATH = Path(__file__).parent / "config.yaml"
@dataclass
class SerialConfig:
"""Serial port configuration"""
receiver_port: str = "/dev/ttyUSB0"
robot_port: str = "/dev/ttyUSB1"
baud_rate: int = 921600
timeout: float = 0.01
auto_detect: bool = True
@dataclass
class ControlConfig:
"""Control loop configuration"""
command_rate_hz: float = 500.0
passthrough_enabled: bool = True
api_command_timeout: float = 1.0 # seconds before API command expires
@dataclass
class SafetyConfig:
"""Safety limits and watchdog settings"""
max_vx: float = 1.5 # m/s forward
max_vy: float = 1.0 # m/s lateral
max_vyaw: float = 0.6 # rad/s yaw
velocity_ramp_rate: float = 2.0 # max m/s^2 acceleration
watchdog_timeout: float = 2.0 # stop if no command for N seconds
max_tilt_degrees: float = 45.0 # emergency stop if tilt exceeds this
enable_watchdog: bool = True
enable_ramp: bool = True
@dataclass
class ServerConfig:
"""API server configuration"""
host: str = "0.0.0.0"
port: int = 8000
log_level: str = "info"
enable_websocket: bool = True
@dataclass
class AppConfig:
"""Top-level application configuration"""
serial: SerialConfig = field(default_factory=SerialConfig)
control: ControlConfig = field(default_factory=ControlConfig)
safety: SafetyConfig = field(default_factory=SafetyConfig)
server: ServerConfig = field(default_factory=ServerConfig)
simulation: bool = False # True = no real serial, just simulate
def load_config() -> AppConfig:
"""
Load configuration with priority:
1. Environment variables (G1_*)
2. config.yaml
3. Defaults
"""
config = AppConfig()
# Try loading YAML config
if CONFIG_PATH.exists():
try:
import yaml # noqa: delay import, optional dep
with open(CONFIG_PATH) as f:
raw = yaml.safe_load(f) or {}
if "serial" in raw:
for k, v in raw["serial"].items():
if hasattr(config.serial, k):
setattr(config.serial, k, v)
if "control" in raw:
for k, v in raw["control"].items():
if hasattr(config.control, k):
setattr(config.control, k, v)
if "safety" in raw:
for k, v in raw["safety"].items():
if hasattr(config.safety, k):
setattr(config.safety, k, v)
if "server" in raw:
for k, v in raw["server"].items():
if hasattr(config.server, k):
setattr(config.server, k, v)
if "simulation" in raw:
config.simulation = bool(raw["simulation"])
logger.info("Loaded config from %s", CONFIG_PATH)
except ImportError:
logger.warning("PyYAML not installed; skipping config.yaml")
except Exception as e:
logger.warning("Failed to load config.yaml: %s", e)
# Environment variable overrides (flat: G1_SERIAL_BAUD_RATE, G1_SIMULATION, etc.)
env_map = {
"G1_SERIAL_RECEIVER_PORT": ("serial", "receiver_port", str),
"G1_SERIAL_ROBOT_PORT": ("serial", "robot_port", str),
"G1_SERIAL_BAUD_RATE": ("serial", "baud_rate", int),
"G1_CONTROL_RATE_HZ": ("control", "command_rate_hz", float),
"G1_SAFETY_MAX_VX": ("safety", "max_vx", float),
"G1_SAFETY_MAX_VY": ("safety", "max_vy", float),
"G1_SAFETY_MAX_VYAW": ("safety", "max_vyaw", float),
"G1_SAFETY_WATCHDOG_TIMEOUT": ("safety", "watchdog_timeout", float),
"G1_SERVER_HOST": ("server", "host", str),
"G1_SERVER_PORT": ("server", "port", int),
"G1_SIMULATION": (None, "simulation", bool),
}
for env_key, (section, attr, typ) in env_map.items():
val = os.environ.get(env_key)
if val is not None:
try:
if typ is bool:
parsed = val.lower() in ("1", "true", "yes")
else:
parsed = typ(val)
if section is None:
setattr(config, attr, parsed)
else:
setattr(getattr(config, section), attr, parsed)
except (ValueError, TypeError):
logger.warning("Invalid env var %s=%s", env_key, val)
return config