aether-robotics 3.7.2__tar.gz → 3.7.3__tar.gz
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.
- {aether_robotics-3.7.2/aether_robotics.egg-info → aether_robotics-3.7.3}/PKG-INFO +2 -2
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/README.md +1 -1
- aether_robotics-3.7.3/VERSION +1 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/__init__.py +1 -1
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/tool_discovery.py +50 -6
- {aether_robotics-3.7.2 → aether_robotics-3.7.3/aether_robotics.egg-info}/PKG-INFO +2 -2
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/pyproject.toml +1 -1
- aether_robotics-3.7.3/tests/test_tool_discovery.py +133 -0
- aether_robotics-3.7.2/VERSION +0 -1
- aether_robotics-3.7.2/tests/test_tool_discovery.py +0 -46
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/LICENSE +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/MANIFEST.in +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/__main__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/actions/__init__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/actions/abstract_actions.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/__init__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/_generator.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/adeept_hat_v3.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/base_adapter.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/drone_adapter.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/freenove_4wd.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/freenove_mecanum.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/l298n_direct_gpio.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/pca9685_generic.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/rover_adapter.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/sunfounder_picarx.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/universal_adapter.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/adapters/waveshare_motor_driver.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/__init__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/adaptation_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/camera_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/correction_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/execution_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/fault_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/memory_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/movement_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/navigation_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/perception_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/planner_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/power_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/task_manager.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/agents/thermal_agent.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/app.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/capabilities/__init__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/capabilities/capability_loader.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/__init__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/auto_installer.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/auto_updater.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/banner.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/calibration.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/executor.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/feedback.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/genome.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/goal_parser.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/llm_planner.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/mapper.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/mavlink_integration.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/memory.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/message_bus.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/metrics.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/navigation_engine.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/planner.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/task_scheduler.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/tool_builder.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/tool_registry.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/core/visualizer.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/faults/__init__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/faults/fault_detector.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/faults/fault_injector.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/simulation/__init__.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/simulation/environment.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/simulation/real_perception.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether/simulation/scenarios.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether_robotics.egg-info/SOURCES.txt +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether_robotics.egg-info/dependency_links.txt +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether_robotics.egg-info/entry_points.txt +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether_robotics.egg-info/requires.txt +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether_robotics.egg-info/top_level.txt +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260331_121709.json +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260331_122446.json +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260401_094544.json +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260401_094554.json +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/drone_v1.json +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/rover_v1.json +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/context/aether_definitions.txt +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/install.sh +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/main.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/requirements.txt +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/setup.cfg +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_auto_adapter.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_auto_updater.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_calibration.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_calibration_unit.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_chaos.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_entrypoint.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_genome.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_mapper.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_mavlink_integration.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_navigation_engine.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_persistent_memory.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_security.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_task_scheduler.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_tool_builder.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/tests/test_yolo_integration.py +0 -0
- {aether_robotics-3.7.2 → aether_robotics-3.7.3}/weights/fault_agent.npy +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aether-robotics
|
|
3
|
-
Version: 3.7.
|
|
3
|
+
Version: 3.7.3
|
|
4
4
|
Summary: Autonomous multi-agent robotics system with DRL-First Hybrid FDIR
|
|
5
5
|
Author: Chahel Paatur
|
|
6
6
|
License: MIT
|
|
@@ -57,7 +57,7 @@ Dynamic: license-file
|
|
|
57
57
|
████████████████████████████████
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
# AETHER v3.7.
|
|
60
|
+
# AETHER v3.7.3 — Autonomous Robotics Operating System
|
|
61
61
|
|
|
62
62
|
> AETHER is the autonomous operating system for robots. Plug in and talk to your robot in plain English and ask it to do anything you want.
|
|
63
63
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
████████████████████████████████
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
# AETHER v3.7.
|
|
19
|
+
# AETHER v3.7.3 — Autonomous Robotics Operating System
|
|
20
20
|
|
|
21
21
|
> AETHER is the autonomous operating system for robots. Plug in and talk to your robot in plain English and ask it to do anything you want.
|
|
22
22
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.7.3
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "3.7.
|
|
1
|
+
__version__ = "3.7.3"
|
|
2
2
|
__author__ = "Chahel Paatur"
|
|
@@ -97,13 +97,49 @@ def _probe_gpio_gpiozero() -> Tuple[bool, Dict]:
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def _probe_i2c() -> Tuple[bool, Dict]:
|
|
100
|
-
"""
|
|
100
|
+
"""Enumerate I2C devices on bus 1 so adapter signature_matches can
|
|
101
|
+
detect HATs by address (e.g. SunFounder Robot HAT at 0x14).
|
|
102
|
+
|
|
103
|
+
smbus2 (shipped in the `pi` install extra) is the authoritative
|
|
104
|
+
enumerator: opening SMBus(1) proves the kernel I2C bus is enabled and
|
|
105
|
+
the user has permission, and read_byte() ACK-probes each address.
|
|
106
|
+
This replaces the old Blinka-only check, which required board+busio
|
|
107
|
+
and so reported "not detected" on real Pi HATs that work fine over
|
|
108
|
+
smbus2.
|
|
109
|
+
|
|
110
|
+
Returns (True, {"type", "available": True, "bus": 1, "devices": [...]})
|
|
111
|
+
on success, or (False, {"available": False, "devices": []}) on any
|
|
112
|
+
failure. The "devices" field is ALWAYS a list, never None. The whole
|
|
113
|
+
body is wrapped so a bug here can never crash boot.
|
|
114
|
+
"""
|
|
101
115
|
try:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
try:
|
|
117
|
+
import smbus2
|
|
118
|
+
except ImportError:
|
|
119
|
+
# No enumerator available — report I2C absent rather than
|
|
120
|
+
# crash. (Blinka was the old path; smbus2 is required for the
|
|
121
|
+
# actual device scan, so its absence means no enumeration.)
|
|
122
|
+
return False, {"available": False, "devices": []}
|
|
123
|
+
|
|
124
|
+
bus = smbus2.SMBus(1)
|
|
125
|
+
devices: List[int] = []
|
|
126
|
+
try:
|
|
127
|
+
for addr in range(0x03, 0x78):
|
|
128
|
+
try:
|
|
129
|
+
bus.read_byte(addr)
|
|
130
|
+
devices.append(addr)
|
|
131
|
+
except OSError:
|
|
132
|
+
pass # no device ACKed at this address
|
|
133
|
+
finally:
|
|
134
|
+
bus.close()
|
|
135
|
+
|
|
136
|
+
print(f" [I2C] Scanned bus 1: found {len(devices)} device(s): "
|
|
137
|
+
f"{[hex(d) for d in devices]}")
|
|
138
|
+
return True, {"type": "smbus2_i2c", "available": True,
|
|
139
|
+
"bus": 1, "devices": devices}
|
|
140
|
+
except Exception:
|
|
141
|
+
# No /dev/i2c-1, permission error, or any unexpected failure.
|
|
142
|
+
return False, {"available": False, "devices": []}
|
|
107
143
|
|
|
108
144
|
|
|
109
145
|
def _probe_mavlink_serial() -> Tuple[bool, Dict]:
|
|
@@ -1203,6 +1239,14 @@ class ToolDiscovery:
|
|
|
1203
1239
|
if key == "oled":
|
|
1204
1240
|
iface = info.get("interface", "spi").upper()
|
|
1205
1241
|
detail = f"{hw_type} {resolution} {iface}"
|
|
1242
|
+
if key == "i2c":
|
|
1243
|
+
devs = info.get("devices", [])
|
|
1244
|
+
bus = info.get("bus", 1)
|
|
1245
|
+
if devs:
|
|
1246
|
+
addrs = ", ".join(hex(d) for d in devs)
|
|
1247
|
+
detail = f"bus {bus}, {len(devs)} device(s) at {addrs}"
|
|
1248
|
+
else:
|
|
1249
|
+
detail = f"bus {bus}, no devices found"
|
|
1206
1250
|
else:
|
|
1207
1251
|
if key == "oled":
|
|
1208
1252
|
if info.get("spi_not_enabled"):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aether-robotics
|
|
3
|
-
Version: 3.7.
|
|
3
|
+
Version: 3.7.3
|
|
4
4
|
Summary: Autonomous multi-agent robotics system with DRL-First Hybrid FDIR
|
|
5
5
|
Author: Chahel Paatur
|
|
6
6
|
License: MIT
|
|
@@ -57,7 +57,7 @@ Dynamic: license-file
|
|
|
57
57
|
████████████████████████████████
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
# AETHER v3.7.
|
|
60
|
+
# AETHER v3.7.3 — Autonomous Robotics Operating System
|
|
61
61
|
|
|
62
62
|
> AETHER is the autonomous operating system for robots. Plug in and talk to your robot in plain English and ask it to do anything you want.
|
|
63
63
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Tests for ToolDiscovery: platform detection, manifest structure."""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
10
|
+
|
|
11
|
+
from aether.core.tool_discovery import ToolDiscovery, _probe_i2c
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture(scope="module")
|
|
15
|
+
def manifest():
|
|
16
|
+
d = ToolDiscovery()
|
|
17
|
+
d.discover()
|
|
18
|
+
return d.manifest
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestToolDiscovery:
|
|
22
|
+
def test_platform_detected(self, manifest):
|
|
23
|
+
assert "platform" in manifest
|
|
24
|
+
assert manifest["platform"] != ""
|
|
25
|
+
|
|
26
|
+
def test_camera_found_or_missing(self, manifest):
|
|
27
|
+
hw = manifest.get("hardware", {})
|
|
28
|
+
cam = hw.get("camera", {})
|
|
29
|
+
# camera key must exist with an 'available' field
|
|
30
|
+
assert "available" in cam
|
|
31
|
+
|
|
32
|
+
def test_manifest_has_capability_score(self, manifest):
|
|
33
|
+
assert "capability_score" in manifest
|
|
34
|
+
score = manifest["capability_score"]
|
|
35
|
+
assert isinstance(score, (int, float))
|
|
36
|
+
assert 0 <= score <= 100
|
|
37
|
+
|
|
38
|
+
def test_available_tools_non_empty(self, manifest):
|
|
39
|
+
tools = manifest.get("available_tools", [])
|
|
40
|
+
assert isinstance(tools, list)
|
|
41
|
+
assert len(tools) > 0
|
|
42
|
+
|
|
43
|
+
def test_missing_capabilities_is_list(self, manifest):
|
|
44
|
+
missing = manifest.get("missing_capabilities", [])
|
|
45
|
+
assert isinstance(missing, list)
|
|
46
|
+
# On a dev machine at least some capabilities will be missing
|
|
47
|
+
# (e.g., GPIO on macOS), so the list should be non-empty
|
|
48
|
+
assert len(missing) > 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ── I2C device enumeration (v3.7.3) ────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _fake_smbus2(ack_addrs=None, instantiation_error=None):
|
|
55
|
+
"""Build a fake smbus2 module whose SMBus(1).read_byte ACKs (returns)
|
|
56
|
+
only at ack_addrs and raises OSError elsewhere. If instantiation_error
|
|
57
|
+
is set, SMBus(bus) raises it (simulates a missing /dev/i2c-N)."""
|
|
58
|
+
ack = set(ack_addrs or [])
|
|
59
|
+
mod = ModuleType("smbus2")
|
|
60
|
+
|
|
61
|
+
class _SMBus:
|
|
62
|
+
def __init__(self, bus):
|
|
63
|
+
if instantiation_error is not None:
|
|
64
|
+
raise instantiation_error
|
|
65
|
+
self.bus = bus
|
|
66
|
+
|
|
67
|
+
def read_byte(self, addr):
|
|
68
|
+
if addr in ack:
|
|
69
|
+
return 0
|
|
70
|
+
raise OSError("no device ACKed")
|
|
71
|
+
|
|
72
|
+
def close(self):
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
mod.SMBus = _SMBus
|
|
76
|
+
return mod
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_probe_i2c_returns_devices_list_on_success():
|
|
80
|
+
fake = _fake_smbus2(ack_addrs=[0x14, 0x40])
|
|
81
|
+
with patch.dict(sys.modules, {"smbus2": fake}):
|
|
82
|
+
ok, info = _probe_i2c()
|
|
83
|
+
assert ok is True
|
|
84
|
+
assert info["available"] is True
|
|
85
|
+
assert info["bus"] == 1
|
|
86
|
+
assert info["type"] == "smbus2_i2c"
|
|
87
|
+
assert info["devices"] == [0x14, 0x40] # ascending scan order
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_probe_i2c_returns_empty_list_on_no_devices():
|
|
91
|
+
fake = _fake_smbus2(ack_addrs=[]) # nothing ACKs
|
|
92
|
+
with patch.dict(sys.modules, {"smbus2": fake}):
|
|
93
|
+
ok, info = _probe_i2c()
|
|
94
|
+
assert ok is True # bus opened fine, just no peripherals
|
|
95
|
+
assert info["devices"] == []
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_probe_i2c_returns_false_on_no_bus():
|
|
99
|
+
# SMBus(1) raises FileNotFoundError — /dev/i2c-1 absent / I2C disabled
|
|
100
|
+
fake = _fake_smbus2(instantiation_error=FileNotFoundError("/dev/i2c-1"))
|
|
101
|
+
with patch.dict(sys.modules, {"smbus2": fake}):
|
|
102
|
+
ok, info = _probe_i2c()
|
|
103
|
+
assert ok is False
|
|
104
|
+
assert info["available"] is False
|
|
105
|
+
assert info["devices"] == []
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_probe_i2c_never_crashes_boot():
|
|
109
|
+
# smbus2 import raises ImportError — must return False cleanly, no crash
|
|
110
|
+
with patch.dict(sys.modules, {"smbus2": None}):
|
|
111
|
+
ok, info = _probe_i2c()
|
|
112
|
+
assert ok is False
|
|
113
|
+
assert info["available"] is False
|
|
114
|
+
assert info["devices"] == []
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_capability_discovery_printer_shows_i2c_with_devices(capsys):
|
|
118
|
+
"""With a device enumerated, the summary's i2c line must read OK with
|
|
119
|
+
the address — never the old '--- not detected'."""
|
|
120
|
+
with patch("aether.core.tool_discovery._probe_i2c",
|
|
121
|
+
return_value=(True, {"type": "smbus2_i2c", "available": True,
|
|
122
|
+
"bus": 1, "devices": [0x14]})):
|
|
123
|
+
d = ToolDiscovery()
|
|
124
|
+
d.discover()
|
|
125
|
+
d.print_summary()
|
|
126
|
+
|
|
127
|
+
out = capsys.readouterr().out
|
|
128
|
+
i2c_line = next(ln for ln in out.splitlines()
|
|
129
|
+
if ln.strip().startswith(("[+] i2c", "[-] i2c")))
|
|
130
|
+
assert "[+]" in i2c_line
|
|
131
|
+
assert "OK" in i2c_line
|
|
132
|
+
assert "0x14" in i2c_line
|
|
133
|
+
assert "not detected" not in i2c_line
|
aether_robotics-3.7.2/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3.7.2
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""Tests for ToolDiscovery: platform detection, manifest structure."""
|
|
2
|
-
import os
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
8
|
-
|
|
9
|
-
from aether.core.tool_discovery import ToolDiscovery
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@pytest.fixture(scope="module")
|
|
13
|
-
def manifest():
|
|
14
|
-
d = ToolDiscovery()
|
|
15
|
-
d.discover()
|
|
16
|
-
return d.manifest
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TestToolDiscovery:
|
|
20
|
-
def test_platform_detected(self, manifest):
|
|
21
|
-
assert "platform" in manifest
|
|
22
|
-
assert manifest["platform"] != ""
|
|
23
|
-
|
|
24
|
-
def test_camera_found_or_missing(self, manifest):
|
|
25
|
-
hw = manifest.get("hardware", {})
|
|
26
|
-
cam = hw.get("camera", {})
|
|
27
|
-
# camera key must exist with an 'available' field
|
|
28
|
-
assert "available" in cam
|
|
29
|
-
|
|
30
|
-
def test_manifest_has_capability_score(self, manifest):
|
|
31
|
-
assert "capability_score" in manifest
|
|
32
|
-
score = manifest["capability_score"]
|
|
33
|
-
assert isinstance(score, (int, float))
|
|
34
|
-
assert 0 <= score <= 100
|
|
35
|
-
|
|
36
|
-
def test_available_tools_non_empty(self, manifest):
|
|
37
|
-
tools = manifest.get("available_tools", [])
|
|
38
|
-
assert isinstance(tools, list)
|
|
39
|
-
assert len(tools) > 0
|
|
40
|
-
|
|
41
|
-
def test_missing_capabilities_is_list(self, manifest):
|
|
42
|
-
missing = manifest.get("missing_capabilities", [])
|
|
43
|
-
assert isinstance(missing, list)
|
|
44
|
-
# On a dev machine at least some capabilities will be missing
|
|
45
|
-
# (e.g., GPIO on macOS), so the list should be non-empty
|
|
46
|
-
assert len(missing) > 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aether_robotics-3.7.2 → aether_robotics-3.7.3}/aether_robotics.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260331_121709.json
RENAMED
|
File without changes
|
{aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260331_122446.json
RENAMED
|
File without changes
|
{aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260401_094544.json
RENAMED
|
File without changes
|
{aether_robotics-3.7.2 → aether_robotics-3.7.3}/configs/calibrated_camera_only_20260401_094554.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|