caasi 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- caasi/__init__.py +7 -0
- caasi/checks/__init__.py +96 -0
- caasi/checks/containers.py +35 -0
- caasi/checks/graphics.py +50 -0
- caasi/checks/hardware.py +47 -0
- caasi/checks/isaac.py +99 -0
- caasi/checks/ml.py +42 -0
- caasi/checks/nvidia.py +70 -0
- caasi/checks/python_env.py +51 -0
- caasi/checks/robotics.py +54 -0
- caasi/checks/ros.py +63 -0
- caasi/checks/simulators.py +41 -0
- caasi/checks/storage.py +39 -0
- caasi/checks/system.py +40 -0
- caasi/checks/vision.py +41 -0
- caasi/cli/__init__.py +1 -0
- caasi/cli/benchmark_cmd.py +111 -0
- caasi/cli/config_cmd.py +126 -0
- caasi/cli/container_cmd.py +107 -0
- caasi/cli/control_cmd.py +132 -0
- caasi/cli/dataset_cmd.py +195 -0
- caasi/cli/definitions.py +114 -0
- caasi/cli/doctor.py +98 -0
- caasi/cli/gpu.py +165 -0
- caasi/cli/init_cmd.py +37 -0
- caasi/cli/lab.py +47 -0
- caasi/cli/main.py +126 -0
- caasi/cli/misc.py +120 -0
- caasi/cli/moveit_cmd.py +151 -0
- caasi/cli/native_cmd.py +119 -0
- caasi/cli/nav_cmd.py +177 -0
- caasi/cli/project_cmd.py +67 -0
- caasi/cli/remote_cmd.py +114 -0
- caasi/cli/replay_cmd.py +75 -0
- caasi/cli/ros_cmd.py +217 -0
- caasi/cli/run_cmd.py +207 -0
- caasi/cli/sensor_cmd.py +105 -0
- caasi/cli/setup_cmd.py +103 -0
- caasi/cli/shell_cmd.py +53 -0
- caasi/cli/sim.py +167 -0
- caasi/cli/system.py +148 -0
- caasi/cli/train_cmd.py +94 -0
- caasi/cli/view_cmd.py +93 -0
- caasi/cli/vision_cmd.py +121 -0
- caasi/core/__init__.py +1 -0
- caasi/core/benchmark.py +46 -0
- caasi/core/config.py +240 -0
- caasi/core/containers.py +56 -0
- caasi/core/dataset.py +196 -0
- caasi/core/experiment.py +178 -0
- caasi/core/nvidia.py +133 -0
- caasi/core/project.py +187 -0
- caasi/core/remotes.py +89 -0
- caasi/core/ros.py +89 -0
- caasi/core/runs.py +369 -0
- caasi/core/sensors.py +130 -0
- caasi/core/viewers.py +99 -0
- caasi/core/vision.py +58 -0
- caasi/i18n/__init__.py +78 -0
- caasi/i18n/en.py +678 -0
- caasi/state.py +69 -0
- caasi/utils/__init__.py +1 -0
- caasi/utils/output.py +64 -0
- caasi/utils/pydist.py +18 -0
- caasi/utils/shell.py +57 -0
- caasi/utils/sysinfo.py +120 -0
- caasi-0.1.0.dist-info/METADATA +178 -0
- caasi-0.1.0.dist-info/RECORD +71 -0
- caasi-0.1.0.dist-info/WHEEL +4 -0
- caasi-0.1.0.dist-info/entry_points.txt +2 -0
- caasi-0.1.0.dist-info/licenses/LICENSE +21 -0
caasi/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Caasi — a lightweight, CLI-first orchestration layer for the robotics ecosystem.
|
|
2
|
+
|
|
3
|
+
Headless-first, CLI-first, process-oriented, and visualization-independent.
|
|
4
|
+
Independent open-source project; not affiliated with or endorsed by NVIDIA.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
caasi/checks/__init__.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Doctor check engine.
|
|
2
|
+
|
|
3
|
+
Checks are grouped into sections. Each check module registers callables that
|
|
4
|
+
receive a :class:`CheckContext` and return :class:`CheckResult` objects. All
|
|
5
|
+
external probes are subprocess/metadata based — the CLI never imports heavy
|
|
6
|
+
robotics libraries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import asdict, dataclass
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Callable, Literal
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
15
|
+
from ..core.config import Config
|
|
16
|
+
|
|
17
|
+
Status = Literal["ok", "warn", "fail", "skip"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class CheckResult:
|
|
22
|
+
section: str
|
|
23
|
+
name: str
|
|
24
|
+
status: Status
|
|
25
|
+
detail: str = ""
|
|
26
|
+
hint: str = ""
|
|
27
|
+
|
|
28
|
+
def to_dict(self) -> dict[str, Any]:
|
|
29
|
+
return asdict(self)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class CheckContext:
|
|
34
|
+
config: "Config"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
CheckFn = Callable[[CheckContext], list[CheckResult]]
|
|
38
|
+
|
|
39
|
+
# Ordered section keys; titles are resolved through i18n at render time.
|
|
40
|
+
SECTION_KEYS: list[str] = [
|
|
41
|
+
"system",
|
|
42
|
+
"hardware",
|
|
43
|
+
"nvidia",
|
|
44
|
+
"graphics",
|
|
45
|
+
"python",
|
|
46
|
+
"isaac",
|
|
47
|
+
"ros",
|
|
48
|
+
"robotics",
|
|
49
|
+
"ml",
|
|
50
|
+
"vision",
|
|
51
|
+
"simulators",
|
|
52
|
+
"containers",
|
|
53
|
+
"storage",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
_REGISTRY: dict[str, list[CheckFn]] = {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def register(section: str) -> Callable[[CheckFn], CheckFn]:
|
|
60
|
+
def decorator(fn: CheckFn) -> CheckFn:
|
|
61
|
+
_REGISTRY.setdefault(section, []).append(fn)
|
|
62
|
+
return fn
|
|
63
|
+
|
|
64
|
+
return decorator
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_checks(
|
|
68
|
+
sections: list[str] | None = None, config: "Config | None" = None
|
|
69
|
+
) -> list[CheckResult]:
|
|
70
|
+
import_all_check_modules()
|
|
71
|
+
from ..state import cfg as state_cfg
|
|
72
|
+
|
|
73
|
+
ctx = CheckContext(config=config or state_cfg())
|
|
74
|
+
results: list[CheckResult] = []
|
|
75
|
+
for key in sections or SECTION_KEYS:
|
|
76
|
+
for fn in _REGISTRY.get(key, []):
|
|
77
|
+
results.extend(fn(ctx))
|
|
78
|
+
return results
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def import_all_check_modules() -> None:
|
|
82
|
+
from . import ( # noqa: F401
|
|
83
|
+
containers,
|
|
84
|
+
graphics,
|
|
85
|
+
hardware,
|
|
86
|
+
isaac,
|
|
87
|
+
ml,
|
|
88
|
+
nvidia,
|
|
89
|
+
python_env,
|
|
90
|
+
robotics,
|
|
91
|
+
ros,
|
|
92
|
+
simulators,
|
|
93
|
+
storage,
|
|
94
|
+
system,
|
|
95
|
+
vision,
|
|
96
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Container runtime checks: docker, podman."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..i18n import _
|
|
6
|
+
from ..utils import shell
|
|
7
|
+
from . import CheckResult, register
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _probe(name: str, binary: str, name_key: str) -> CheckResult:
|
|
11
|
+
path = shell.which(binary)
|
|
12
|
+
if not path:
|
|
13
|
+
return CheckResult(name, _(name_key), "skip", _("doctor.containers.not_installed"))
|
|
14
|
+
result = shell.run_cmd([binary, "--version"], timeout=5)
|
|
15
|
+
if result.ok:
|
|
16
|
+
return CheckResult(name, _(name_key), "ok", result.stdout.strip().splitlines()[0])
|
|
17
|
+
return CheckResult(name, _(name_key), "warn", result.stderr.strip() or _("doctor.containers.error"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@register("containers")
|
|
21
|
+
def check_containers(ctx) -> list[CheckResult]:
|
|
22
|
+
docker = _probe("containers", "docker", "doctor.containers.docker")
|
|
23
|
+
podman = _probe("containers", "podman", "doctor.containers.podman")
|
|
24
|
+
results = [docker, podman]
|
|
25
|
+
if docker.status == "skip" and podman.status == "skip":
|
|
26
|
+
results.append(
|
|
27
|
+
CheckResult(
|
|
28
|
+
"containers",
|
|
29
|
+
_("doctor.containers.title"),
|
|
30
|
+
"warn",
|
|
31
|
+
_("doctor.containers.none"),
|
|
32
|
+
_("doctor.containers.hint"),
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
return results
|
caasi/checks/graphics.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Graphics checks: Vulkan availability, display server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from ..i18n import _
|
|
8
|
+
from ..utils import shell
|
|
9
|
+
from . import CheckResult, register
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register("graphics")
|
|
13
|
+
def check_graphics(ctx) -> list[CheckResult]:
|
|
14
|
+
results: list[CheckResult] = []
|
|
15
|
+
|
|
16
|
+
if shell.which("vulkaninfo"):
|
|
17
|
+
results.append(CheckResult("graphics", _("doctor.graphics.vulkan"), "ok", "vulkaninfo"))
|
|
18
|
+
else:
|
|
19
|
+
ld = shell.run_cmd(["ldconfig", "-p"], timeout=5)
|
|
20
|
+
if ld.ok and "libvulkan" in ld.stdout:
|
|
21
|
+
results.append(
|
|
22
|
+
CheckResult(
|
|
23
|
+
"graphics", _("doctor.graphics.vulkan"), "ok", _("doctor.graphics.vulkan_lib")
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
else:
|
|
27
|
+
results.append(
|
|
28
|
+
CheckResult(
|
|
29
|
+
"graphics",
|
|
30
|
+
_("doctor.graphics.vulkan"),
|
|
31
|
+
"warn",
|
|
32
|
+
_("doctor.graphics.vulkan_missing"),
|
|
33
|
+
_("doctor.graphics.vulkan_hint"),
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if os.environ.get("WAYLAND_DISPLAY") or os.environ.get("DISPLAY"):
|
|
38
|
+
results.append(
|
|
39
|
+
CheckResult("graphics", _("doctor.graphics.display"), "ok", _("doctor.graphics.display_on"))
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
results.append(
|
|
43
|
+
CheckResult(
|
|
44
|
+
"graphics",
|
|
45
|
+
_("doctor.graphics.display"),
|
|
46
|
+
"skip",
|
|
47
|
+
_("doctor.graphics.display_off"),
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
return results
|
caasi/checks/hardware.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Hardware checks: CPU, RAM."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from ..i18n import _
|
|
8
|
+
from ..utils import sysinfo
|
|
9
|
+
from . import CheckResult, register
|
|
10
|
+
|
|
11
|
+
RAM_WARN_KIB = 16 * 1024 * 1024 # below 16 GiB is tight for Isaac workflows
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register("hardware")
|
|
15
|
+
def check_hardware(ctx) -> list[CheckResult]:
|
|
16
|
+
results: list[CheckResult] = []
|
|
17
|
+
|
|
18
|
+
model = sysinfo.cpu_model()
|
|
19
|
+
cores = os.cpu_count()
|
|
20
|
+
if model or cores:
|
|
21
|
+
detail = ", ".join(part for part in (model, f"{cores} cores") if part)
|
|
22
|
+
results.append(CheckResult("hardware", _("doctor.hardware.cpu"), "ok", detail))
|
|
23
|
+
else:
|
|
24
|
+
results.append(
|
|
25
|
+
CheckResult(
|
|
26
|
+
"hardware", _("doctor.hardware.cpu"), "warn", "", _("doctor.hardware.cpu_hint")
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
meminfo = sysinfo.read_meminfo()
|
|
31
|
+
total_kib = meminfo.get("MemTotal")
|
|
32
|
+
available_kib = meminfo.get("MemAvailable")
|
|
33
|
+
if total_kib:
|
|
34
|
+
detail = _("doctor.hardware.ram_detail").format(
|
|
35
|
+
total=total_kib / (1024 * 1024),
|
|
36
|
+
available=(available_kib or 0) / (1024 * 1024),
|
|
37
|
+
)
|
|
38
|
+
status = "ok" if total_kib >= RAM_WARN_KIB else "warn"
|
|
39
|
+
hint = "" if status == "ok" else _("doctor.hardware.ram_hint")
|
|
40
|
+
results.append(CheckResult("hardware", _("doctor.hardware.ram"), status, detail, hint))
|
|
41
|
+
else:
|
|
42
|
+
results.append(
|
|
43
|
+
CheckResult(
|
|
44
|
+
"hardware", _("doctor.hardware.ram"), "warn", "", _("doctor.hardware.ram_hint")
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
return results
|
caasi/checks/isaac.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Isaac Sim / Isaac Lab detection.
|
|
2
|
+
|
|
3
|
+
Detection order per tool: configured tool registry -> environment variables
|
|
4
|
+
-> common install locations -> pip metadata. Reused by `caasi info`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import glob
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from ..core.config import Config
|
|
14
|
+
from ..i18n import _
|
|
15
|
+
from ..utils import pydist
|
|
16
|
+
from . import CheckResult, register
|
|
17
|
+
|
|
18
|
+
Detection = tuple[str, str, str] # (status, detail, hint)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _glob_first(patterns: list[str]) -> str | None:
|
|
22
|
+
for pattern in patterns:
|
|
23
|
+
matches = sorted(glob.glob(os.path.expanduser(pattern)))
|
|
24
|
+
if matches:
|
|
25
|
+
return matches[-1]
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def detect_isaac_sim(config: Config) -> Detection:
|
|
30
|
+
tool = config.resolve_tool("isaacsim")
|
|
31
|
+
if tool is not None:
|
|
32
|
+
path = tool.expanded_path
|
|
33
|
+
if path is not None and path.exists():
|
|
34
|
+
label = f" {tool.version}" if tool.version else ""
|
|
35
|
+
return "ok", _("doctor.isaac.sim_found").format(version=label, path=str(path)), ""
|
|
36
|
+
return (
|
|
37
|
+
"warn",
|
|
38
|
+
_("doctor.isaac.sim_configured_missing").format(path=tool.path or "?"),
|
|
39
|
+
_("doctor.isaac.config_hint"),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
env_path = os.environ.get("ISAACSIM_PATH")
|
|
43
|
+
if env_path and Path(env_path).expanduser().exists():
|
|
44
|
+
return "ok", _("doctor.isaac.sim_found").format(version="", path=env_path), ""
|
|
45
|
+
|
|
46
|
+
common = _glob_first(
|
|
47
|
+
[
|
|
48
|
+
"~/isaacsim",
|
|
49
|
+
"~/.local/share/ov/pkg/isaac-sim-*",
|
|
50
|
+
"~/.local/share/ov/pkg/isaac_sim-*",
|
|
51
|
+
"/opt/isaac-sim*",
|
|
52
|
+
"/opt/isaacsim*",
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
if common:
|
|
56
|
+
return "ok", _("doctor.isaac.sim_found").format(version="", path=common), ""
|
|
57
|
+
|
|
58
|
+
version = pydist.pip_version("isaacsim")
|
|
59
|
+
if version:
|
|
60
|
+
return "ok", _("doctor.isaac.sim_pip").format(version=version), ""
|
|
61
|
+
|
|
62
|
+
return "fail", _("doctor.isaac.sim_missing"), _("doctor.isaac.sim_hint")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def detect_isaac_lab(config: Config) -> Detection:
|
|
66
|
+
tool = config.resolve_tool("isaaclab")
|
|
67
|
+
if tool is not None:
|
|
68
|
+
path = tool.expanded_path
|
|
69
|
+
if path is not None and path.exists():
|
|
70
|
+
label = f" {tool.version}" if tool.version else ""
|
|
71
|
+
return "ok", _("doctor.isaac.lab_found").format(version=label, path=str(path)), ""
|
|
72
|
+
return (
|
|
73
|
+
"warn",
|
|
74
|
+
_("doctor.isaac.lab_configured_missing").format(path=tool.path or "?"),
|
|
75
|
+
_("doctor.isaac.config_hint"),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
env_path = os.environ.get("ISAACLAB_PATH")
|
|
79
|
+
if env_path and Path(env_path).expanduser().exists():
|
|
80
|
+
return "ok", _("doctor.isaac.lab_found").format(version="", path=env_path), ""
|
|
81
|
+
|
|
82
|
+
common = _glob_first(["~/isaaclab", "~/IsaacLab", "~/workspace/isaaclab", "~/workspace/IsaacLab"])
|
|
83
|
+
if common:
|
|
84
|
+
return "ok", _("doctor.isaac.lab_found").format(version="", path=common), ""
|
|
85
|
+
|
|
86
|
+
version = pydist.pip_version("isaaclab")
|
|
87
|
+
if version:
|
|
88
|
+
return "ok", _("doctor.isaac.lab_pip").format(version=version), ""
|
|
89
|
+
|
|
90
|
+
return "fail", _("doctor.isaac.lab_missing"), _("doctor.isaac.lab_hint")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@register("isaac")
|
|
94
|
+
def check_isaac(ctx) -> list[CheckResult]:
|
|
95
|
+
status, detail, hint = detect_isaac_sim(ctx.config)
|
|
96
|
+
results = [CheckResult("isaac", _("doctor.isaac.sim"), status, detail, hint)]
|
|
97
|
+
status, detail, hint = detect_isaac_lab(ctx.config)
|
|
98
|
+
results.append(CheckResult("isaac", _("doctor.isaac.lab"), status, detail, hint))
|
|
99
|
+
return results
|
caasi/checks/ml.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""ML stack checks: PyTorch, TensorRT, ONNX Runtime, cuDNN (metadata only)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..i18n import _
|
|
6
|
+
from ..utils import pydist, shell
|
|
7
|
+
from . import CheckResult, register
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _pip_check(section: str, name_key: str, dists: tuple[str, ...], hint_key: str) -> CheckResult:
|
|
11
|
+
version = pydist.pip_version(*dists)
|
|
12
|
+
if version:
|
|
13
|
+
return CheckResult(section, _(name_key), "ok", version)
|
|
14
|
+
return CheckResult(section, _(name_key), "fail", _("doctor.ml.not_installed"), _(hint_key))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register("ml")
|
|
18
|
+
def check_ml(ctx) -> list[CheckResult]:
|
|
19
|
+
results = [
|
|
20
|
+
_pip_check("ml", "doctor.ml.torch", ("torch",), "doctor.ml.torch_hint"),
|
|
21
|
+
_pip_check("ml", "doctor.ml.tensorrt", ("tensorrt", "tensorrt_libs"), "doctor.ml.tensorrt_hint"),
|
|
22
|
+
_pip_check(
|
|
23
|
+
"ml", "doctor.ml.onnx", ("onnxruntime-gpu", "onnxruntime"), "doctor.ml.onnx_hint"
|
|
24
|
+
),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
cudnn = pydist.pip_version("nvidia-cudnn-cu12", "nvidia-cudnn-cu11")
|
|
28
|
+
if cudnn:
|
|
29
|
+
results.append(CheckResult("ml", _("doctor.ml.cudnn"), "ok", cudnn))
|
|
30
|
+
else:
|
|
31
|
+
ld = shell.run_cmd(["ldconfig", "-p"], timeout=5)
|
|
32
|
+
if ld.ok and "libcudnn" in ld.stdout:
|
|
33
|
+
results.append(
|
|
34
|
+
CheckResult("ml", _("doctor.ml.cudnn"), "ok", _("doctor.ml.cudnn_system"))
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
results.append(
|
|
38
|
+
CheckResult(
|
|
39
|
+
"ml", _("doctor.ml.cudnn"), "warn", _("doctor.ml.not_installed"), _("doctor.ml.cudnn_hint")
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
return results
|
caasi/checks/nvidia.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""NVIDIA checks: driver, GPUs, CUDA (via nvidia-smi)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..core import nvidia
|
|
6
|
+
from ..i18n import _
|
|
7
|
+
from . import CheckResult, register
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register("nvidia")
|
|
11
|
+
def check_nvidia(ctx) -> list[CheckResult]:
|
|
12
|
+
if not nvidia.available():
|
|
13
|
+
return [
|
|
14
|
+
CheckResult(
|
|
15
|
+
"nvidia",
|
|
16
|
+
_("doctor.nvidia.smi"),
|
|
17
|
+
"fail",
|
|
18
|
+
_("doctor.nvidia.smi_missing_detail"),
|
|
19
|
+
_("doctor.nvidia.smi_hint"),
|
|
20
|
+
)
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
results: list[CheckResult] = []
|
|
24
|
+
try:
|
|
25
|
+
gpus = nvidia.query(["index", "name", "memory.total", "driver_version"])
|
|
26
|
+
except nvidia.NvidiaSmiError as exc:
|
|
27
|
+
return [
|
|
28
|
+
CheckResult(
|
|
29
|
+
"nvidia",
|
|
30
|
+
_("doctor.nvidia.smi"),
|
|
31
|
+
"fail",
|
|
32
|
+
str(exc),
|
|
33
|
+
_("doctor.nvidia.smi_hint"),
|
|
34
|
+
)
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
driver = gpus[0].get("driver_version") if gpus else None
|
|
38
|
+
results.append(
|
|
39
|
+
CheckResult(
|
|
40
|
+
"nvidia",
|
|
41
|
+
_("doctor.nvidia.driver"),
|
|
42
|
+
"ok",
|
|
43
|
+
_("doctor.nvidia.driver_detail").format(driver=driver or "?"),
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
for gpu in gpus:
|
|
47
|
+
vram = gpu.get("memory.total")
|
|
48
|
+
detail = _("doctor.nvidia.gpu_detail").format(
|
|
49
|
+
name=gpu.get("name", "?"),
|
|
50
|
+
vram=f"{vram / 1024:.1f} GiB" if isinstance(vram, (int, float)) else "?",
|
|
51
|
+
)
|
|
52
|
+
results.append(CheckResult("nvidia", _("doctor.nvidia.gpu"), "ok", detail))
|
|
53
|
+
|
|
54
|
+
cuda = nvidia.cuda_version()
|
|
55
|
+
if cuda:
|
|
56
|
+
results.append(
|
|
57
|
+
CheckResult(
|
|
58
|
+
"nvidia",
|
|
59
|
+
_("doctor.nvidia.cuda"),
|
|
60
|
+
"ok",
|
|
61
|
+
_("doctor.nvidia.cuda_detail").format(version=cuda),
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
results.append(
|
|
66
|
+
CheckResult(
|
|
67
|
+
"nvidia", _("doctor.nvidia.cuda"), "warn", "", _("doctor.nvidia.cuda_hint")
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
return results
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Python environment checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from ..i18n import _
|
|
10
|
+
from . import CheckResult, register
|
|
11
|
+
|
|
12
|
+
MINIMUM = (3, 10)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@register("python")
|
|
16
|
+
def check_python(ctx) -> list[CheckResult]:
|
|
17
|
+
results: list[CheckResult] = []
|
|
18
|
+
version = platform.python_version()
|
|
19
|
+
if sys.version_info[:2] >= MINIMUM:
|
|
20
|
+
results.append(
|
|
21
|
+
CheckResult(
|
|
22
|
+
"python", _("doctor.python.version"), "ok", f"Python {version} ({sys.executable})"
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
else:
|
|
26
|
+
results.append(
|
|
27
|
+
CheckResult(
|
|
28
|
+
"python",
|
|
29
|
+
_("doctor.python.version"),
|
|
30
|
+
"fail",
|
|
31
|
+
f"Python {version}",
|
|
32
|
+
_("doctor.python.version_hint"),
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
conda_env = os.environ.get("CONDA_DEFAULT_ENV")
|
|
37
|
+
if conda_env:
|
|
38
|
+
results.append(
|
|
39
|
+
CheckResult(
|
|
40
|
+
"python", _("doctor.python.env"), "ok", _("doctor.python.conda").format(env=conda_env)
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
elif sys.prefix != sys.base_prefix:
|
|
44
|
+
results.append(
|
|
45
|
+
CheckResult("python", _("doctor.python.env"), "ok", _("doctor.python.venv"))
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
results.append(
|
|
49
|
+
CheckResult("python", _("doctor.python.env"), "skip", _("doctor.python.system_env"))
|
|
50
|
+
)
|
|
51
|
+
return results
|
caasi/checks/robotics.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Robotics stack checks: Nav2, MoveIt 2, ros2_control (best-effort via ros2 CLI)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..core import ros as ros_core
|
|
6
|
+
from ..i18n import _
|
|
7
|
+
from ..utils import shell
|
|
8
|
+
from . import CheckResult, register
|
|
9
|
+
|
|
10
|
+
STACKS = [
|
|
11
|
+
("nav2", "nav2_bringup", "doctor.robotics.nav2"),
|
|
12
|
+
("moveit2", "moveit_core", "doctor.robotics.moveit"),
|
|
13
|
+
("ros2_control", "controller_manager", "doctor.robotics.control"),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
PKG_TIMEOUT = 8.0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@register("robotics")
|
|
20
|
+
def check_robotics(ctx) -> list[CheckResult]:
|
|
21
|
+
ros2 = ros_core.find_ros2_binary()
|
|
22
|
+
if not ros2:
|
|
23
|
+
return [
|
|
24
|
+
CheckResult(
|
|
25
|
+
"robotics",
|
|
26
|
+
_("doctor.robotics.title"),
|
|
27
|
+
"skip",
|
|
28
|
+
_("doctor.robotics.no_ros2"),
|
|
29
|
+
_("doctor.robotics.no_ros2_hint"),
|
|
30
|
+
)
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
results: list[CheckResult] = []
|
|
34
|
+
for stack, package, name_key in STACKS:
|
|
35
|
+
probe = shell.run_cmd([ros2, "pkg", "prefix", package], timeout=PKG_TIMEOUT)
|
|
36
|
+
if probe.ok:
|
|
37
|
+
results.append(CheckResult("robotics", _(name_key), "ok", package))
|
|
38
|
+
elif probe.returncode == -1:
|
|
39
|
+
results.append(
|
|
40
|
+
CheckResult(
|
|
41
|
+
"robotics", _(name_key), "warn", _("doctor.robotics.timeout").format(package=package)
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
else:
|
|
45
|
+
results.append(
|
|
46
|
+
CheckResult(
|
|
47
|
+
"robotics",
|
|
48
|
+
_(name_key),
|
|
49
|
+
"fail",
|
|
50
|
+
_("doctor.robotics.not_installed").format(package=package),
|
|
51
|
+
_("doctor.robotics.hint").format(stack=stack),
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
return results
|
caasi/checks/ros.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""ROS 2 checks: distro, setup file, ros2 CLI, middleware."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from ..core import ros as ros_core
|
|
8
|
+
from ..i18n import _
|
|
9
|
+
from . import CheckResult, register
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register("ros")
|
|
13
|
+
def check_ros(ctx) -> list[CheckResult]:
|
|
14
|
+
distro, root = ros_core.find_distro()
|
|
15
|
+
if distro is None:
|
|
16
|
+
return [
|
|
17
|
+
CheckResult(
|
|
18
|
+
"ros",
|
|
19
|
+
_("doctor.ros.distro"),
|
|
20
|
+
"fail",
|
|
21
|
+
_("doctor.ros.missing"),
|
|
22
|
+
_("doctor.ros.hint"),
|
|
23
|
+
)
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
results: list[CheckResult] = []
|
|
27
|
+
results.append(CheckResult("ros", _("doctor.ros.distro"), "ok", distro))
|
|
28
|
+
|
|
29
|
+
if root is not None and (root / "setup.bash").exists():
|
|
30
|
+
results.append(
|
|
31
|
+
CheckResult(
|
|
32
|
+
"ros", _("doctor.ros.setup"), "ok", str(root / "setup.bash")
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
|
+
results.append(
|
|
37
|
+
CheckResult(
|
|
38
|
+
"ros",
|
|
39
|
+
_("doctor.ros.setup"),
|
|
40
|
+
"warn",
|
|
41
|
+
_("doctor.ros.setup_missing").format(distro=distro),
|
|
42
|
+
_("doctor.ros.setup_hint"),
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
ros2 = ros_core.find_ros2_binary()
|
|
47
|
+
if ros2:
|
|
48
|
+
results.append(CheckResult("ros", _("doctor.ros.cli"), "ok", ros2))
|
|
49
|
+
else:
|
|
50
|
+
results.append(
|
|
51
|
+
CheckResult(
|
|
52
|
+
"ros", _("doctor.ros.cli"), "warn", _("doctor.ros.cli_missing"), _("doctor.ros.hint")
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
rmw = os.environ.get("RMW_IMPLEMENTATION")
|
|
57
|
+
if rmw:
|
|
58
|
+
results.append(CheckResult("ros", _("doctor.ros.rmw"), "ok", rmw))
|
|
59
|
+
else:
|
|
60
|
+
results.append(
|
|
61
|
+
CheckResult("ros", _("doctor.ros.rmw"), "skip", _("doctor.ros.rmw_default"))
|
|
62
|
+
)
|
|
63
|
+
return results
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Alternative simulator checks: Gazebo, MuJoCo."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..i18n import _
|
|
6
|
+
from ..utils import pydist, shell
|
|
7
|
+
from . import CheckResult, register
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register("simulators")
|
|
11
|
+
def check_simulators(ctx) -> list[CheckResult]:
|
|
12
|
+
results: list[CheckResult] = []
|
|
13
|
+
|
|
14
|
+
gz = shell.which("gz") or shell.which("gazebo")
|
|
15
|
+
if gz:
|
|
16
|
+
results.append(CheckResult("simulators", _("doctor.sims.gazebo"), "ok", gz))
|
|
17
|
+
else:
|
|
18
|
+
results.append(
|
|
19
|
+
CheckResult(
|
|
20
|
+
"simulators",
|
|
21
|
+
_("doctor.sims.gazebo"),
|
|
22
|
+
"skip",
|
|
23
|
+
_("doctor.sims.not_installed"),
|
|
24
|
+
_("doctor.sims.gazebo_hint"),
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
mujoco = pydist.pip_version("mujoco")
|
|
29
|
+
if mujoco:
|
|
30
|
+
results.append(CheckResult("simulators", _("doctor.sims.mujoco"), "ok", mujoco))
|
|
31
|
+
else:
|
|
32
|
+
results.append(
|
|
33
|
+
CheckResult(
|
|
34
|
+
"simulators",
|
|
35
|
+
_("doctor.sims.mujoco"),
|
|
36
|
+
"skip",
|
|
37
|
+
_("doctor.sims.not_installed"),
|
|
38
|
+
_("doctor.sims.mujoco_hint"),
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
return results
|