Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b992d0d
chore: fix linting and import sorting issues
Yasserelhaddar Jan 23, 2026
e2fc875
feat(hardware): add scanner package dependencies
Yasserelhaddar Jan 23, 2026
66908c7
refactor(stereo): extract base stereo camera backend class
Yasserelhaddar Jan 23, 2026
fda3644
feat(scanners_3d): add core scanner interfaces and models
Yasserelhaddar Jan 23, 2026
b414e8b
feat(scanners_3d): add Photoneo backend implementation
Yasserelhaddar Jan 23, 2026
cbd7c1f
feat(scanners_3d): add HTTP service for scanner operations
Yasserelhaddar Jan 23, 2026
928e58f
feat(cli): add scanner CLI commands
Yasserelhaddar Jan 23, 2026
dc4eaab
test(scanners_3d): add unit and integration tests
Yasserelhaddar Jan 23, 2026
b8c9498
fix(tests): improve hardware test skip conditions
Yasserelhaddar Jan 23, 2026
03f0e6a
docs: update hardware documentation
Yasserelhaddar Jan 23, 2026
3c76e7f
fix(test): mock HARVESTERS_AVAILABLE in Photoneo backend tests
Yasserelhaddar Feb 9, 2026
d76bfef
feat(scanners_3d): align MockPhotoneoBackend with real backend API
Yasserelhaddar Feb 11, 2026
c871211
feat(scanners_3d): add MockPhotoneo backend support to AsyncScanner3D…
Yasserelhaddar Feb 11, 2026
ca51855
fix(scanners_3d): rewrite setup_photoneo.py for reliable cross-platfo…
Yasserelhaddar Feb 11, 2026
4d8e547
docs(scanners_3d): add comprehensive README and update hardware README
Yasserelhaddar Feb 11, 2026
adbd461
merge dev
vik-rant Feb 18, 2026
1c90e03
ruff
vik-rant Feb 18, 2026
2d87945
Merge branch 'dev' into hardware/feature/photoneo
vik-rant Feb 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 249 additions & 9 deletions mindtrace/hardware/README.md

Large diffs are not rendered by default.

84 changes: 83 additions & 1 deletion mindtrace/hardware/mindtrace/hardware/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ A command-line interface for managing Mindtrace hardware services with process l
- **`mindtrace-hw stereo status`** - Show stereo camera service status with URLs
- **`mindtrace-hw stereo logs`** - View stereo camera service logs

**3D Scanner Services:**
- **`mindtrace-hw scanner start`** - Start 3D scanner API service
- **`mindtrace-hw scanner stop`** - Stop 3D scanner service gracefully
- **`mindtrace-hw scanner status`** - Show 3D scanner service status with URLs
- **`mindtrace-hw scanner logs`** - View 3D scanner service logs

**PLC Services:**
- **`mindtrace-hw plc start`** - Start PLC API service
- **`mindtrace-hw plc stop`** - Stop PLC service gracefully
Expand All @@ -27,7 +33,7 @@ A command-line interface for managing Mindtrace hardware services with process l
**Global Commands:**
- **`mindtrace-hw status`** - Show all hardware services status
- **`mindtrace-hw stop`** - Stop all services cleanly
- **`mindtrace-hw logs <service>`** - View logs for camera, plc, stereo, or all services
- **`mindtrace-hw logs <service>`** - View logs for camera, plc, scanner, stereo, or all services

### Key Capabilities

Expand Down Expand Up @@ -71,6 +77,21 @@ mindtrace-hw stereo status
mindtrace-hw stereo stop
```

**3D Scanner Services:**
```bash
# Start 3D scanner API service
mindtrace-hw scanner start

# Open documentation in browser automatically
mindtrace-hw scanner start --open-docs

# Check 3D scanner service status
mindtrace-hw scanner status

# Stop 3D scanner service
mindtrace-hw scanner stop
```

**PLC Services:**
```bash
# Start PLC API service
Expand Down Expand Up @@ -117,6 +138,7 @@ cli/
├── commands/
│ ├── __init__.py
│ ├── camera.py # Camera service management commands
│ ├── scanner.py # 3D scanner service management commands
│ ├── stereo.py # Stereo camera service management commands
│ ├── plc.py # PLC service management commands
│ └── status.py # Global status command
Expand All @@ -133,6 +155,7 @@ cli/
### Service Integration Architecture
- **Camera API**: Managed via `mindtrace.hardware.services.cameras.launcher`
- **Stereo Camera API**: Managed via `mindtrace.hardware.services.stereo_cameras.launcher`
- **3D Scanner API**: Managed via `mindtrace.hardware.services.scanners_3d.launcher`
- **PLC API**: Managed via `mindtrace.hardware.services.plcs.launcher`
- **Process Coordination**: PID tracking in `~/.mindtrace/hw_services.json`
- **Health Monitoring**: TCP port checks and process validation
Expand Down Expand Up @@ -204,6 +227,11 @@ Configure services using standardized environment variables:
- `STEREO_CAMERA_API_PORT` - API service port (default: `8004`)
- `STEREO_CAMERA_API_URL` - Full API URL (default: `http://localhost:8004`)

### 3D Scanner API Service Configuration
- `SCANNER_3D_API_HOST` - API service host (default: `localhost`)
- `SCANNER_3D_API_PORT` - API service port (default: `8005`)
- `SCANNER_3D_API_URL` - Full API URL (default: `http://localhost:8005`)

### PLC API Service Configuration
- `PLC_API_HOST` - API service host (default: `localhost`)
- `PLC_API_PORT` - API service port (default: `8003`)
Expand Down Expand Up @@ -335,6 +363,59 @@ mindtrace-hw stereo logs
# - Console output for API service
```

### 3D Scanner Commands

#### scanner start
```bash
mindtrace-hw scanner start [OPTIONS]

Options:
--api-host TEXT 3D Scanner API service host [default: localhost]
--api-port INTEGER 3D Scanner API service port [default: 8005]
--open-docs Open API documentation in browser

Examples:
# Start with default settings
mindtrace-hw scanner start

# Start on custom host/port
mindtrace-hw scanner start --api-host 0.0.0.0 --api-port 8006

# Start and open Swagger UI in browser
mindtrace-hw scanner start --open-docs

Access URLs:
- API: http://localhost:8005
- Swagger UI: http://localhost:8005/docs
- ReDoc: http://localhost:8005/redoc
```

#### scanner stop
```bash
mindtrace-hw scanner stop

# Gracefully stops 3D scanner API service
```

#### scanner status
```bash
mindtrace-hw scanner status

# Shows detailed status:
# - Service running status
# - Process ID and resource usage
# - Host:Port and access URLs
# - Service uptime
```

#### scanner logs
```bash
mindtrace-hw scanner logs

# Provides guidance on log locations:
# - Console output for API service
```

### PLC Commands

#### plc start
Expand Down Expand Up @@ -463,6 +544,7 @@ The CLI architecture supports easy addition of new hardware services:
### Implemented Services
- **Camera Management**: `mindtrace-hw camera start/stop/status/logs`
- **Stereo Camera Management**: `mindtrace-hw stereo start/stop/status/logs`
- **3D Scanner Management**: `mindtrace-hw scanner start/stop/status/logs`
- **PLC Management**: `mindtrace-hw plc start/stop/status`

### Planned Services
Expand Down
17 changes: 14 additions & 3 deletions mindtrace/hardware/mindtrace/hardware/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from mindtrace.hardware.cli.commands.camera import app as camera_app
from mindtrace.hardware.cli.commands.plc import app as plc_app
from mindtrace.hardware.cli.commands.scanner import app as scanner_app
from mindtrace.hardware.cli.commands.status import status_command
from mindtrace.hardware.cli.commands.stereo import app as stereo_app
from mindtrace.hardware.cli.core.logger import RichLogger, setup_logger
Expand All @@ -26,6 +27,7 @@
# Add subcommand apps
app.add_typer(camera_app, name="camera", help="Manage camera services")
app.add_typer(plc_app, name="plc", help="Manage PLC services")
app.add_typer(scanner_app, name="scanner", help="Manage 3D scanner services")
app.add_typer(stereo_app, name="stereo", help="Manage stereo camera services")


Expand Down Expand Up @@ -77,15 +79,20 @@ def stop():
# Show what will be stopped
camera_services = [s for s in running_services if "camera_api" in s or "configurator" in s]
plc_services = [s for s in running_services if "plc" in s]
scanner_services = [s for s in running_services if "scanner" in s]
stereo_services = [s for s in running_services if "stereo" in s]
other_services = [
s for s in running_services if s not in camera_services and s not in plc_services and s not in stereo_services
s
for s in running_services
if s not in camera_services and s not in plc_services and s not in scanner_services and s not in stereo_services
]

if camera_services:
logger.info(f"Stopping camera services: {', '.join(camera_services)}")
if plc_services:
logger.info(f"Stopping PLC services: {', '.join(plc_services)}")
if scanner_services:
logger.info(f"Stopping 3D scanner services: {', '.join(scanner_services)}")
if stereo_services:
logger.info(f"Stopping stereo camera services: {', '.join(stereo_services)}")
if other_services:
Expand All @@ -99,13 +106,13 @@ def stop():

@app.command()
def logs(
service: Annotated[str, typer.Argument(help="Service name (camera, plc, stereo, all)")],
service: Annotated[str, typer.Argument(help="Service name (camera, plc, scanner, stereo, all)")],
follow: Annotated[bool, typer.Option("--follow", "-f", help="Follow log output")] = False,
):
"""View service logs."""
logger = RichLogger()

valid_services = ["camera", "plc", "stereo", "all"]
valid_services = ["camera", "plc", "scanner", "stereo", "all"]
if service not in valid_services:
logger.error(f"Invalid service: {service}. Must be one of: {', '.join(valid_services)}")
raise typer.Exit(1)
Expand All @@ -119,6 +126,10 @@ def logs(
log_locations = [
"API logs: Check console output where service was started",
]
elif service == "scanner":
log_locations = [
"API logs: Check console output where service was started",
]
elif service == "stereo":
log_locations = [
"API logs: Check console output where service was started",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from mindtrace.hardware.cli.commands.camera import app as camera_app
from mindtrace.hardware.cli.commands.plc import app as plc_app
from mindtrace.hardware.cli.commands.scanner import app as scanner_app
from mindtrace.hardware.cli.commands.status import status_command
from mindtrace.hardware.cli.commands.stereo import app as stereo_app

__all__ = ["camera_app", "plc_app", "stereo_app", "status_command"]
__all__ = ["camera_app", "plc_app", "scanner_app", "stereo_app", "status_command"]
123 changes: 123 additions & 0 deletions mindtrace/hardware/mindtrace/hardware/cli/commands/scanner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""3D Scanner service commands."""

import time
import webbrowser

import typer
from typing_extensions import Annotated

from mindtrace.hardware.cli.core.logger import RichLogger
from mindtrace.hardware.cli.core.process_manager import ProcessManager
from mindtrace.hardware.cli.utils.display import console, format_status
from mindtrace.hardware.cli.utils.network import ServiceTimeoutError, is_port_available, wait_for_service

app = typer.Typer(help="Manage 3D scanner services")


@app.command()
def start(
api_host: Annotated[
str, typer.Option("--api-host", help="3D Scanner API service host", envvar="SCANNER_3D_API_HOST")
] = "localhost",
api_port: Annotated[
int, typer.Option("--api-port", help="3D Scanner API service port", envvar="SCANNER_3D_API_PORT")
] = 8005,
open_docs: Annotated[bool, typer.Option("--open-docs", help="Open API documentation in browser")] = False,
):
"""Start 3D scanner API service."""
logger = RichLogger()
pm = ProcessManager()

# Check if service is already running
if pm.is_service_running("scanner_3d_api"):
logger.warning("3D Scanner API is already running")
if not typer.confirm("Stop existing service and restart?"):
return
pm.stop_service("scanner_3d_api")
time.sleep(1)

# Check port availability
if not is_port_available(api_host, api_port):
logger.error(f"Port {api_port} is already in use on {api_host}")
return

try:
# Start API service with spinner
with console.status("[cyan]Starting 3D Scanner API...", spinner="dots") as status:
pm.start_scanner_3d_api(api_host, api_port)

# Wait for API to be ready
try:
wait_for_service(api_host, api_port, timeout=10)
status.update("[green]3D Scanner API started")
except ServiceTimeoutError:
logger.error("3D Scanner API failed to start (timeout)")
pm.stop_service("scanner_3d_api")
return

logger.success(f"3D Scanner API started → http://{api_host}:{api_port}")
logger.info(f" Swagger UI: http://{api_host}:{api_port}/docs")
logger.info(f" ReDoc: http://{api_host}:{api_port}/redoc")

# Open browser if requested
if open_docs:
docs_url = f"http://{api_host}:{api_port}/docs"
webbrowser.open(docs_url)
logger.info(f"Opening browser: {docs_url}")

logger.info("\nService running in background. Use 'mindtrace-hw scanner stop' to stop.")

except Exception as e:
logger.error(f"Failed to start service: {e}")


@app.command()
def stop():
"""Stop 3D scanner API service."""
logger = RichLogger()
pm = ProcessManager()

logger.info("Stopping 3D Scanner API...")

if pm.is_service_running("scanner_3d_api"):
pm.stop_service("scanner_3d_api")
logger.success("3D Scanner API stopped")
else:
logger.info("3D Scanner API was not running")


@app.command()
def status():
"""Show 3D scanner service status."""
pm = ProcessManager()

# Get status for 3D scanner service
all_status = pm.get_status()
scanner_status = {k: v for k, v in all_status.items() if k == "scanner_3d_api"}

if not scanner_status:
typer.echo("3D Scanner API is not configured.")
typer.echo("\nUse 'mindtrace-hw scanner start' to launch the service.")
return

typer.echo("\n3D Scanner Service Status:")
format_status(scanner_status)

# Show additional info if service is running
if scanner_status.get("scanner_3d_api", {}).get("running"):
info = scanner_status["scanner_3d_api"]
url = f"http://{info['host']}:{info['port']}"
typer.echo("\nAccess URLs:")
typer.echo(f" API: {url}")
typer.echo(f" Swagger UI: {url}/docs")
typer.echo(f" ReDoc: {url}/redoc")


@app.command()
def logs():
"""View 3D scanner service logs."""
logger = RichLogger()

logger.info("Log viewing not yet implemented.")
logger.info("Logs can be found in:")
logger.info(" - API logs: Check console output where service was started")
Loading