foodforthought-cli 0.2.7__py3-none-any.whl → 0.3.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.
- ate/__init__.py +6 -0
- ate/__main__.py +16 -0
- ate/auth/__init__.py +1 -0
- ate/auth/device_flow.py +141 -0
- ate/auth/token_store.py +96 -0
- ate/behaviors/__init__.py +100 -0
- ate/behaviors/approach.py +399 -0
- ate/behaviors/common.py +686 -0
- ate/behaviors/tree.py +454 -0
- ate/cli.py +855 -3995
- ate/client.py +90 -0
- ate/commands/__init__.py +168 -0
- ate/commands/auth.py +389 -0
- ate/commands/bridge.py +448 -0
- ate/commands/data.py +185 -0
- ate/commands/deps.py +111 -0
- ate/commands/generate.py +384 -0
- ate/commands/memory.py +907 -0
- ate/commands/parts.py +166 -0
- ate/commands/primitive.py +399 -0
- ate/commands/protocol.py +288 -0
- ate/commands/recording.py +524 -0
- ate/commands/repo.py +154 -0
- ate/commands/simulation.py +291 -0
- ate/commands/skill.py +303 -0
- ate/commands/skills.py +487 -0
- ate/commands/team.py +147 -0
- ate/commands/workflow.py +271 -0
- ate/detection/__init__.py +38 -0
- ate/detection/base.py +142 -0
- ate/detection/color_detector.py +399 -0
- ate/detection/trash_detector.py +322 -0
- ate/drivers/__init__.py +39 -0
- ate/drivers/ble_transport.py +405 -0
- ate/drivers/mechdog.py +942 -0
- ate/drivers/wifi_camera.py +477 -0
- ate/interfaces/__init__.py +187 -0
- ate/interfaces/base.py +273 -0
- ate/interfaces/body.py +267 -0
- ate/interfaces/detection.py +282 -0
- ate/interfaces/locomotion.py +422 -0
- ate/interfaces/manipulation.py +408 -0
- ate/interfaces/navigation.py +389 -0
- ate/interfaces/perception.py +362 -0
- ate/interfaces/sensors.py +247 -0
- ate/interfaces/types.py +371 -0
- ate/llm_proxy.py +239 -0
- ate/mcp_server.py +387 -0
- ate/memory/__init__.py +35 -0
- ate/memory/cloud.py +244 -0
- ate/memory/context.py +269 -0
- ate/memory/embeddings.py +184 -0
- ate/memory/export.py +26 -0
- ate/memory/merge.py +146 -0
- ate/memory/migrate/__init__.py +34 -0
- ate/memory/migrate/base.py +89 -0
- ate/memory/migrate/pipeline.py +189 -0
- ate/memory/migrate/sources/__init__.py +13 -0
- ate/memory/migrate/sources/chroma.py +170 -0
- ate/memory/migrate/sources/pinecone.py +120 -0
- ate/memory/migrate/sources/qdrant.py +110 -0
- ate/memory/migrate/sources/weaviate.py +160 -0
- ate/memory/reranker.py +353 -0
- ate/memory/search.py +26 -0
- ate/memory/store.py +548 -0
- ate/recording/__init__.py +83 -0
- ate/recording/demonstration.py +378 -0
- ate/recording/session.py +415 -0
- ate/recording/upload.py +304 -0
- ate/recording/visual.py +416 -0
- ate/recording/wrapper.py +95 -0
- ate/robot/__init__.py +221 -0
- ate/robot/agentic_servo.py +856 -0
- ate/robot/behaviors.py +493 -0
- ate/robot/ble_capture.py +1000 -0
- ate/robot/ble_enumerate.py +506 -0
- ate/robot/calibration.py +668 -0
- ate/robot/calibration_state.py +388 -0
- ate/robot/commands.py +3735 -0
- ate/robot/direction_calibration.py +554 -0
- ate/robot/discovery.py +441 -0
- ate/robot/introspection.py +330 -0
- ate/robot/llm_system_id.py +654 -0
- ate/robot/locomotion_calibration.py +508 -0
- ate/robot/manager.py +270 -0
- ate/robot/marker_generator.py +611 -0
- ate/robot/perception.py +502 -0
- ate/robot/primitives.py +614 -0
- ate/robot/profiles.py +281 -0
- ate/robot/registry.py +322 -0
- ate/robot/servo_mapper.py +1153 -0
- ate/robot/skill_upload.py +675 -0
- ate/robot/target_calibration.py +500 -0
- ate/robot/teach.py +515 -0
- ate/robot/types.py +242 -0
- ate/robot/visual_labeler.py +1048 -0
- ate/robot/visual_servo_loop.py +494 -0
- ate/robot/visual_servoing.py +570 -0
- ate/robot/visual_system_id.py +906 -0
- ate/transports/__init__.py +121 -0
- ate/transports/base.py +394 -0
- ate/transports/ble.py +405 -0
- ate/transports/hybrid.py +444 -0
- ate/transports/serial.py +345 -0
- ate/urdf/__init__.py +30 -0
- ate/urdf/capture.py +582 -0
- ate/urdf/cloud.py +491 -0
- ate/urdf/collision.py +271 -0
- ate/urdf/commands.py +708 -0
- ate/urdf/depth.py +360 -0
- ate/urdf/inertial.py +312 -0
- ate/urdf/kinematics.py +330 -0
- ate/urdf/lifting.py +415 -0
- ate/urdf/meshing.py +300 -0
- ate/urdf/models/__init__.py +110 -0
- ate/urdf/models/depth_anything.py +253 -0
- ate/urdf/models/sam2.py +324 -0
- ate/urdf/motion_analysis.py +396 -0
- ate/urdf/pipeline.py +468 -0
- ate/urdf/scale.py +256 -0
- ate/urdf/scan_session.py +411 -0
- ate/urdf/segmentation.py +299 -0
- ate/urdf/synthesis.py +319 -0
- ate/urdf/topology.py +336 -0
- ate/urdf/validation.py +371 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
- foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
- foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
ate/robot/discovery.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-discovery of robots on network and USB.
|
|
3
|
+
|
|
4
|
+
Scans for:
|
|
5
|
+
- Serial devices matching known patterns
|
|
6
|
+
- Network cameras (ESP32-CAM, IP cameras)
|
|
7
|
+
- BLE devices (ESP32, HM-10, etc.)
|
|
8
|
+
- ROS2 topics (if ROS2 available)
|
|
9
|
+
- mDNS services
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import glob
|
|
14
|
+
import time
|
|
15
|
+
import platform
|
|
16
|
+
import concurrent.futures
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import List, Optional, Dict, Any
|
|
19
|
+
from enum import Enum, auto
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import serial
|
|
23
|
+
import serial.tools.list_ports
|
|
24
|
+
HAS_SERIAL = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
HAS_SERIAL = False
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import requests
|
|
30
|
+
HAS_REQUESTS = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
HAS_REQUESTS = False
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
from bleak import BleakScanner
|
|
36
|
+
HAS_BLEAK = True
|
|
37
|
+
except ImportError:
|
|
38
|
+
HAS_BLEAK = False
|
|
39
|
+
|
|
40
|
+
from .registry import KNOWN_ROBOTS, RobotType, ConnectionType
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DiscoveryStatus(Enum):
|
|
44
|
+
"""Status of discovered device."""
|
|
45
|
+
FOUND = auto() # Device found
|
|
46
|
+
IDENTIFIED = auto() # Device identified as known robot
|
|
47
|
+
CONNECTED = auto() # Successfully connected
|
|
48
|
+
ERROR = auto() # Error during discovery
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class DiscoveredRobot:
|
|
53
|
+
"""A robot discovered on the network or USB."""
|
|
54
|
+
robot_type: Optional[str] = None # ID of matched robot type
|
|
55
|
+
name: str = "" # Display name
|
|
56
|
+
status: DiscoveryStatus = DiscoveryStatus.FOUND
|
|
57
|
+
|
|
58
|
+
# Connection info
|
|
59
|
+
connection: Optional[ConnectionType] = None
|
|
60
|
+
port: Optional[str] = None # Serial port
|
|
61
|
+
ip: Optional[str] = None # Network IP
|
|
62
|
+
ports: Dict[str, int] = field(default_factory=dict) # Network ports
|
|
63
|
+
|
|
64
|
+
# Additional info
|
|
65
|
+
manufacturer: Optional[str] = None
|
|
66
|
+
model: Optional[str] = None
|
|
67
|
+
firmware: Optional[str] = None
|
|
68
|
+
|
|
69
|
+
# Raw data
|
|
70
|
+
raw_data: Dict[str, Any] = field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def discover_robots(
|
|
74
|
+
timeout: float = 5.0,
|
|
75
|
+
scan_serial: bool = True,
|
|
76
|
+
scan_network: bool = True,
|
|
77
|
+
scan_ble: bool = True,
|
|
78
|
+
network_subnet: Optional[str] = None,
|
|
79
|
+
) -> List[DiscoveredRobot]:
|
|
80
|
+
"""
|
|
81
|
+
Discover all robots on network, USB, and BLE.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
timeout: Timeout for network/BLE scans
|
|
85
|
+
scan_serial: Scan USB serial devices
|
|
86
|
+
scan_network: Scan network for cameras/robots
|
|
87
|
+
scan_ble: Scan for BLE devices
|
|
88
|
+
network_subnet: Subnet to scan (e.g., "192.168.1")
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of discovered robots
|
|
92
|
+
"""
|
|
93
|
+
discovered = []
|
|
94
|
+
|
|
95
|
+
if scan_serial:
|
|
96
|
+
discovered.extend(discover_serial_robots())
|
|
97
|
+
|
|
98
|
+
if scan_network:
|
|
99
|
+
discovered.extend(discover_network_cameras(
|
|
100
|
+
timeout=timeout,
|
|
101
|
+
subnet=network_subnet
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
if scan_ble:
|
|
105
|
+
discovered.extend(discover_ble_robots(timeout=timeout))
|
|
106
|
+
|
|
107
|
+
return discovered
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def discover_serial_robots() -> List[DiscoveredRobot]:
|
|
111
|
+
"""
|
|
112
|
+
Discover robots connected via USB serial.
|
|
113
|
+
|
|
114
|
+
Matches connected serial devices against known robot patterns.
|
|
115
|
+
"""
|
|
116
|
+
if not HAS_SERIAL:
|
|
117
|
+
return []
|
|
118
|
+
|
|
119
|
+
discovered = []
|
|
120
|
+
ports = serial.tools.list_ports.comports()
|
|
121
|
+
|
|
122
|
+
for port in ports:
|
|
123
|
+
robot = DiscoveredRobot(
|
|
124
|
+
connection=ConnectionType.SERIAL,
|
|
125
|
+
port=port.device,
|
|
126
|
+
raw_data={
|
|
127
|
+
"vid": port.vid,
|
|
128
|
+
"pid": port.pid,
|
|
129
|
+
"serial_number": port.serial_number,
|
|
130
|
+
"manufacturer": port.manufacturer,
|
|
131
|
+
"product": port.product,
|
|
132
|
+
"description": port.description,
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Try to identify robot type by matching patterns
|
|
137
|
+
for robot_type in KNOWN_ROBOTS.values():
|
|
138
|
+
if ConnectionType.SERIAL not in robot_type.connection_types:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
for pattern in robot_type.serial_patterns:
|
|
142
|
+
# Convert glob pattern to check
|
|
143
|
+
if _matches_pattern(port.device, pattern):
|
|
144
|
+
robot.robot_type = robot_type.id
|
|
145
|
+
robot.name = f"{robot_type.name} ({port.device})"
|
|
146
|
+
robot.manufacturer = robot_type.manufacturer
|
|
147
|
+
robot.model = robot_type.name
|
|
148
|
+
robot.status = DiscoveryStatus.IDENTIFIED
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
if robot.robot_type:
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if not robot.robot_type:
|
|
155
|
+
robot.name = f"Unknown Device ({port.device})"
|
|
156
|
+
robot.manufacturer = port.manufacturer
|
|
157
|
+
robot.model = port.product
|
|
158
|
+
|
|
159
|
+
discovered.append(robot)
|
|
160
|
+
|
|
161
|
+
return discovered
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def discover_network_cameras(
|
|
165
|
+
timeout: float = 2.0,
|
|
166
|
+
subnet: Optional[str] = None,
|
|
167
|
+
max_workers: int = 50,
|
|
168
|
+
) -> List[DiscoveredRobot]:
|
|
169
|
+
"""
|
|
170
|
+
Discover network cameras (ESP32-CAM, IP cameras).
|
|
171
|
+
|
|
172
|
+
Scans common camera endpoints on the local subnet.
|
|
173
|
+
"""
|
|
174
|
+
if not HAS_REQUESTS:
|
|
175
|
+
return []
|
|
176
|
+
|
|
177
|
+
# Determine subnet to scan
|
|
178
|
+
if subnet is None:
|
|
179
|
+
subnet = _get_local_subnet()
|
|
180
|
+
if subnet is None:
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
discovered = []
|
|
184
|
+
ips_to_scan = [f"{subnet}.{i}" for i in range(1, 255)]
|
|
185
|
+
|
|
186
|
+
def check_camera(ip: str) -> Optional[DiscoveredRobot]:
|
|
187
|
+
"""Check if IP has a camera endpoint."""
|
|
188
|
+
try:
|
|
189
|
+
# Try ESP32-CAM status endpoint
|
|
190
|
+
response = requests.get(
|
|
191
|
+
f"http://{ip}/status",
|
|
192
|
+
timeout=timeout
|
|
193
|
+
)
|
|
194
|
+
if response.status_code == 200:
|
|
195
|
+
return DiscoveredRobot(
|
|
196
|
+
robot_type=None, # Camera only, not a robot
|
|
197
|
+
name=f"ESP32-CAM ({ip})",
|
|
198
|
+
status=DiscoveryStatus.FOUND,
|
|
199
|
+
connection=ConnectionType.WIFI,
|
|
200
|
+
ip=ip,
|
|
201
|
+
ports={"camera_port": 80, "camera_stream_port": 81},
|
|
202
|
+
raw_data={"type": "esp32cam", "response": response.text[:200]},
|
|
203
|
+
)
|
|
204
|
+
except requests.RequestException:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
# Try generic camera snapshot
|
|
209
|
+
response = requests.get(
|
|
210
|
+
f"http://{ip}/capture",
|
|
211
|
+
timeout=timeout,
|
|
212
|
+
stream=True
|
|
213
|
+
)
|
|
214
|
+
if response.status_code == 200:
|
|
215
|
+
content_type = response.headers.get("Content-Type", "")
|
|
216
|
+
if "image" in content_type:
|
|
217
|
+
return DiscoveredRobot(
|
|
218
|
+
name=f"Network Camera ({ip})",
|
|
219
|
+
status=DiscoveryStatus.FOUND,
|
|
220
|
+
connection=ConnectionType.WIFI,
|
|
221
|
+
ip=ip,
|
|
222
|
+
ports={"camera_port": 80},
|
|
223
|
+
raw_data={"type": "generic_camera"},
|
|
224
|
+
)
|
|
225
|
+
except requests.RequestException:
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
# Parallel scan
|
|
231
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
232
|
+
futures = {executor.submit(check_camera, ip): ip for ip in ips_to_scan}
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
for future in concurrent.futures.as_completed(futures, timeout=timeout * 3):
|
|
236
|
+
try:
|
|
237
|
+
result = future.result(timeout=0.1)
|
|
238
|
+
if result:
|
|
239
|
+
discovered.append(result)
|
|
240
|
+
except Exception:
|
|
241
|
+
pass
|
|
242
|
+
except TimeoutError:
|
|
243
|
+
# Some futures didn't complete in time, that's ok
|
|
244
|
+
pass
|
|
245
|
+
|
|
246
|
+
return discovered
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def discover_ble_robots(
|
|
250
|
+
timeout: float = 10.0,
|
|
251
|
+
name_patterns: List[str] = None,
|
|
252
|
+
) -> List[DiscoveredRobot]:
|
|
253
|
+
"""
|
|
254
|
+
Discover robots via Bluetooth Low Energy.
|
|
255
|
+
|
|
256
|
+
Scans for BLE devices matching known robot name patterns.
|
|
257
|
+
Common ESP32-based robots advertise names like "MechDog", "ESP32", etc.
|
|
258
|
+
"""
|
|
259
|
+
if not HAS_BLEAK:
|
|
260
|
+
return []
|
|
261
|
+
|
|
262
|
+
import asyncio
|
|
263
|
+
|
|
264
|
+
# Default patterns for known robots
|
|
265
|
+
if name_patterns is None:
|
|
266
|
+
name_patterns = [
|
|
267
|
+
"MechDog",
|
|
268
|
+
"HiWonder",
|
|
269
|
+
"ESP32",
|
|
270
|
+
"ESP-",
|
|
271
|
+
"Unitree",
|
|
272
|
+
"Robot",
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
async def scan():
|
|
276
|
+
devices = await BleakScanner.discover(timeout=timeout)
|
|
277
|
+
return devices
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
# Run the async scan
|
|
281
|
+
devices = asyncio.run(scan())
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print(f"BLE scan error: {e}")
|
|
284
|
+
return []
|
|
285
|
+
|
|
286
|
+
discovered = []
|
|
287
|
+
for device in devices:
|
|
288
|
+
name = device.name or ""
|
|
289
|
+
|
|
290
|
+
# Check if name matches any pattern
|
|
291
|
+
matched_pattern = None
|
|
292
|
+
for pattern in name_patterns:
|
|
293
|
+
if pattern.lower() in name.lower():
|
|
294
|
+
matched_pattern = pattern
|
|
295
|
+
break
|
|
296
|
+
|
|
297
|
+
if not matched_pattern:
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
# Try to identify robot type
|
|
301
|
+
robot_type = None
|
|
302
|
+
for rt in KNOWN_ROBOTS.values():
|
|
303
|
+
if ConnectionType.BLUETOOTH not in rt.connection_types:
|
|
304
|
+
continue
|
|
305
|
+
if rt.name.lower() in name.lower() or rt.manufacturer.lower() in name.lower():
|
|
306
|
+
robot_type = rt.id
|
|
307
|
+
break
|
|
308
|
+
|
|
309
|
+
robot = DiscoveredRobot(
|
|
310
|
+
robot_type=robot_type,
|
|
311
|
+
name=f"{name} ({device.address})",
|
|
312
|
+
status=DiscoveryStatus.IDENTIFIED if robot_type else DiscoveryStatus.FOUND,
|
|
313
|
+
connection=ConnectionType.BLUETOOTH,
|
|
314
|
+
port=device.address, # BLE address used as "port"
|
|
315
|
+
raw_data={
|
|
316
|
+
"ble_address": device.address,
|
|
317
|
+
"ble_name": name,
|
|
318
|
+
"rssi": device.rssi if hasattr(device, 'rssi') else None,
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
discovered.append(robot)
|
|
322
|
+
|
|
323
|
+
return discovered
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def probe_serial_device(port: str, baud_rate: int = 115200) -> Optional[Dict[str, Any]]:
|
|
327
|
+
"""
|
|
328
|
+
Probe a serial device to identify what type of robot it is.
|
|
329
|
+
|
|
330
|
+
Sends identification commands and parses response.
|
|
331
|
+
"""
|
|
332
|
+
if not HAS_SERIAL:
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
with serial.Serial(port, baud_rate, timeout=2) as ser:
|
|
337
|
+
time.sleep(0.5)
|
|
338
|
+
|
|
339
|
+
# Try MicroPython REPL identification
|
|
340
|
+
ser.write(b'\x03') # Ctrl+C
|
|
341
|
+
time.sleep(0.2)
|
|
342
|
+
ser.write(b'\x02') # Ctrl+B for friendly REPL
|
|
343
|
+
time.sleep(0.3)
|
|
344
|
+
|
|
345
|
+
# Check for MicroPython
|
|
346
|
+
ser.write(b'import sys; print(sys.implementation)\r\n')
|
|
347
|
+
time.sleep(0.5)
|
|
348
|
+
response = ser.read(1000).decode('utf-8', errors='ignore')
|
|
349
|
+
|
|
350
|
+
if 'micropython' in response.lower():
|
|
351
|
+
info = {"type": "micropython", "response": response}
|
|
352
|
+
|
|
353
|
+
# Try to detect MechDog
|
|
354
|
+
ser.write(b'from HW_MechDog import MechDog\r\n')
|
|
355
|
+
time.sleep(0.3)
|
|
356
|
+
response2 = ser.read(500).decode('utf-8', errors='ignore')
|
|
357
|
+
|
|
358
|
+
if 'Error' not in response2 and 'Traceback' not in response2:
|
|
359
|
+
info["robot"] = "hiwonder_mechdog"
|
|
360
|
+
|
|
361
|
+
return info
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
return {"error": str(e)}
|
|
365
|
+
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _matches_pattern(device: str, pattern: str) -> bool:
|
|
370
|
+
"""Check if device matches a glob pattern."""
|
|
371
|
+
import fnmatch
|
|
372
|
+
return fnmatch.fnmatch(device, pattern)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _get_local_subnet() -> Optional[str]:
|
|
376
|
+
"""Get the local network subnet (e.g., '192.168.1')."""
|
|
377
|
+
system = platform.system()
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
import socket
|
|
381
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
382
|
+
s.connect(("8.8.8.8", 80))
|
|
383
|
+
ip = s.getsockname()[0]
|
|
384
|
+
s.close()
|
|
385
|
+
|
|
386
|
+
# Extract subnet
|
|
387
|
+
parts = ip.split(".")
|
|
388
|
+
if len(parts) == 4:
|
|
389
|
+
return ".".join(parts[:3])
|
|
390
|
+
except Exception:
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def quick_scan() -> Dict[str, Any]:
|
|
397
|
+
"""
|
|
398
|
+
Quick scan for common robot configurations.
|
|
399
|
+
|
|
400
|
+
Returns summary of what was found.
|
|
401
|
+
"""
|
|
402
|
+
result = {
|
|
403
|
+
"serial_ports": [],
|
|
404
|
+
"network_cameras": [],
|
|
405
|
+
"ble_devices": [],
|
|
406
|
+
"identified_robots": [],
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
# Scan serial
|
|
410
|
+
serial_robots = discover_serial_robots()
|
|
411
|
+
for robot in serial_robots:
|
|
412
|
+
result["serial_ports"].append({
|
|
413
|
+
"port": robot.port,
|
|
414
|
+
"type": robot.robot_type,
|
|
415
|
+
"name": robot.name,
|
|
416
|
+
})
|
|
417
|
+
if robot.robot_type:
|
|
418
|
+
result["identified_robots"].append(robot)
|
|
419
|
+
|
|
420
|
+
# Quick network scan (just local subnet, fast timeout)
|
|
421
|
+
network_cameras = discover_network_cameras(timeout=1.0)
|
|
422
|
+
for camera in network_cameras:
|
|
423
|
+
result["network_cameras"].append({
|
|
424
|
+
"ip": camera.ip,
|
|
425
|
+
"name": camera.name,
|
|
426
|
+
"ports": camera.ports,
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
# Quick BLE scan
|
|
430
|
+
ble_robots = discover_ble_robots(timeout=3.0)
|
|
431
|
+
for robot in ble_robots:
|
|
432
|
+
result["ble_devices"].append({
|
|
433
|
+
"address": robot.port,
|
|
434
|
+
"name": robot.name,
|
|
435
|
+
"type": robot.robot_type,
|
|
436
|
+
"rssi": robot.raw_data.get("rssi"),
|
|
437
|
+
})
|
|
438
|
+
if robot.robot_type:
|
|
439
|
+
result["identified_robots"].append(robot)
|
|
440
|
+
|
|
441
|
+
return result
|