Skip to content

Commit c60e761

Browse files
committed
refactor(yolo-coral-tpu): migrate from docker to native python execution
1 parent 991c9e8 commit c60e761

4 files changed

Lines changed: 100 additions & 190 deletions

File tree

skills/detection/yolo-detection-2026-coral-tpu/Dockerfile

Lines changed: 0 additions & 55 deletions
This file was deleted.

skills/detection/yolo-detection-2026-coral-tpu/SKILL.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,24 @@ version: 1.0.0
55
icon: assets/icon.png
66
entry: scripts/monitor.js
77
deploy: deploy.sh
8-
runtime: docker
8+
runtime: node
99

1010
requirements:
11-
docker: ">=20.10"
12-
usb: true
1311
platforms: ["linux", "macos", "windows"]
1412

13+
manual_setup:
14+
macos: |
15+
# 1. Fix Homebrew permissions and install libusb
16+
sudo chown -R $(whoami) /opt/homebrew
17+
brew install libusb
18+
19+
# 2. Add Apple Silicon Edge TPU native driver
20+
curl -sSLO https://github.com/feranick/libedgetpu/releases/download/16.0TF2.19.1-1/libedgetpu-16.0-tf2.19.1-1_MacOS_Silicon.zip
21+
unzip -q -o libedgetpu-16.0-tf2.19.1-1_MacOS_Silicon.zip
22+
sudo mkdir -p /usr/local/lib
23+
sudo cp libedgetpu.1.dylib /usr/local/lib/
24+
rm -rf libedgetpu*
25+
1526
parameters:
1627
- name: auto_start
1728
label: "Auto Start"
Lines changed: 59 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,109 @@
11
#!/usr/bin/env bash
2-
# deploy.sh — Docker-based bootstrapper for Coral TPU Detection Skill
2+
# deploy.sh — Native local bootstrapper for Coral TPU Detection Skill
33
#
4-
# Builds the Docker image locally and verifies Edge TPU connectivity.
4+
# Builds a local Python virtual environment and verifies Edge TPU connectivity.
55
# Called by Aegis skill-runtime-manager during installation.
66
#
77
# Exit codes:
88
# 0 = success
9-
# 1 = fatal error (Docker not found)
9+
# 1 = fatal error (Python/pip not found)
1010
# 2 = partial success (no TPU detected, will use CPU fallback)
1111

1212
set -euo pipefail
1313

1414
SKILL_DIR="$(cd "$(dirname "$0")" && pwd)"
15-
IMAGE_NAME="aegis-coral-tpu"
16-
IMAGE_TAG="latest"
1715
LOG_PREFIX="[coral-tpu-deploy]"
1816

1917
log() { echo "$LOG_PREFIX $*" >&2; }
2018
emit() { echo "$1"; } # JSON to stdout for Aegis to parse
2119

22-
# ─── Step 1: Check Docker ────────────────────────────────────────────────────
23-
24-
find_docker() {
25-
for cmd in docker podman; do
26-
if command -v "$cmd" &>/dev/null; then
27-
echo "$cmd"
28-
return 0
29-
fi
30-
done
31-
return 1
32-
}
33-
34-
DOCKER_CMD=$(find_docker) || {
35-
log "ERROR: Docker (or Podman) not found. Install Docker Desktop 4.35+ and retry."
36-
emit '{"event": "error", "stage": "docker", "message": "Docker not found. Install Docker Desktop 4.35+"}'
37-
exit 1
38-
}
20+
# ─── Step 1: Detect Platform ────────────────────────────────────────────────
21+
22+
PLATFORM="$(uname -s)"
23+
ARCH="$(uname -m)"
24+
log "Platform: $PLATFORM ($ARCH)"
25+
emit "{\"event\": \"progress\", \"stage\": \"platform\", \"message\": \"Platform: $PLATFORM/$ARCH\"}"
3926

40-
# Verify Docker is running
41-
if ! "$DOCKER_CMD" info &>/dev/null; then
42-
log "ERROR: Docker daemon is not running. Start Docker Desktop and retry."
43-
emit '{"event": "error", "stage": "docker", "message": "Docker daemon not running"}'
27+
if [ "$PLATFORM" = "Linux" ]; then
28+
log "Linux: ensuring system packages are installed..."
29+
emit '{"event": "progress", "stage": "platform", "message": "Ensuring Linux dependencies..."}'
30+
sudo apt-get update >/dev/null 2>&1 || true
31+
sudo apt-get install -y --no-install-recommends \
32+
python3 python3-pip python3-venv libusb-1.0-0 >/dev/null 2>&1 || true
33+
fi
34+
35+
# ─── Step 2: Ensure Python 3 ────────────────────────────────────────────────
36+
37+
if ! command -v python3 &>/dev/null; then
38+
log "ERROR: Python 3 not found."
39+
emit '{"event": "error", "stage": "python", "message": "Python 3 not found"}'
4440
exit 1
4541
fi
4642

47-
DOCKER_VER=$("$DOCKER_CMD" version --format '{{.Server.Version}}' 2>/dev/null || echo "unknown")
48-
log "Using $DOCKER_CMD (version: $DOCKER_VER)"
49-
emit "{\"event\": \"progress\", \"stage\": \"docker\", \"message\": \"Docker ready ($DOCKER_VER)\"}"
43+
PYTHON_CMD="python3"
44+
log "Using Python: $($PYTHON_CMD --version)"
45+
emit '{"event": "progress", "stage": "python", "message": "Python verified"}'
5046

51-
# ─── Step 2: Detect platform for USB access hints ───────────────────────────
47+
# ─── Step 3: Create Virtual Environment ─────────────────────────────────────
5248

53-
PLATFORM="$(uname -s)"
54-
ARCH="$(uname -m)"
55-
USB_FLAG=""
56-
57-
case "$PLATFORM" in
58-
Linux)
59-
USB_FLAG="--device /dev/bus/usb"
60-
log "Platform: Linux — will use --device /dev/bus/usb"
61-
;;
62-
Darwin)
63-
log "Platform: macOS ($ARCH) — Docker Desktop 4.35+ USB/IP required"
64-
log "Ensure Docker Desktop Settings → Features → USB devices is enabled"
65-
# macOS Docker Desktop 4.35+ handles USB/IP transparently
66-
# No --device flag needed, but privileged may be required
67-
USB_FLAG="--privileged"
68-
;;
69-
MINGW*|MSYS*|CYGWIN*)
70-
log "Platform: Windows — Docker Desktop 4.35+ USB/IP or WSL2 backend"
71-
USB_FLAG="--privileged"
72-
;;
73-
*)
74-
log "Platform: Unknown ($PLATFORM) — attempting with --privileged"
75-
USB_FLAG="--privileged"
76-
;;
77-
esac
49+
VENV_DIR="$SKILL_DIR/venv"
50+
log "Setting up virtual environment in $VENV_DIR..."
51+
emit '{"event": "progress", "stage": "build", "message": "Creating Python virtual environment..."}'
7852

79-
emit "{\"event\": \"progress\", \"stage\": \"platform\", \"message\": \"Platform: $PLATFORM/$ARCH\"}"
53+
"$PYTHON_CMD" -m venv "$VENV_DIR"
8054

81-
# ─── Step 3: Build Docker image ─────────────────────────────────────────────
55+
# Ensure the venv works
56+
if [ ! -f "$VENV_DIR/bin/python" ]; then
57+
log "ERROR: Failed to create virtual environment."
58+
emit '{"event": "error", "stage": "build", "message": "Failed to create venv"}'
59+
exit 1
60+
fi
8261

83-
log "Building Docker image: $IMAGE_NAME:$IMAGE_TAG ..."
84-
emit '{"event": "progress", "stage": "build", "message": "Building Docker image (this may take a few minutes)..."}'
62+
# ─── Step 4: Install Dependencies ───────────────────────────────────────────
8563

86-
if "$DOCKER_CMD" build -t "$IMAGE_NAME:$IMAGE_TAG" "$SKILL_DIR" 2>&1 | while read -r line; do
87-
log "$line"
88-
done; then
89-
log "Docker image built successfully"
90-
emit '{"event": "progress", "stage": "build", "message": "Docker image ready"}'
91-
else
92-
log "ERROR: Docker build failed"
93-
emit '{"event": "error", "stage": "build", "message": "Docker image build failed"}'
64+
log "Installing Python dependencies (this may take a minute)..."
65+
emit '{"event": "progress", "stage": "build", "message": "Installing ai-edge-litert and dependencies..."}'
66+
67+
# Upgrade pip securely
68+
"$VENV_DIR/bin/python" -m pip install --upgrade pip >/dev/null 2>&1 || true
69+
70+
# Install requirements
71+
if ! "$VENV_DIR/bin/python" -m pip install -r "$SKILL_DIR/requirements.txt"; then
72+
log "ERROR: Failed to install Python dependencies."
73+
emit '{"event": "error", "stage": "build", "message": "pip install failed"}'
9474
exit 1
9575
fi
9676

97-
# ─── Step 4: Probe for Edge TPU devices ──────────────────────────────────────
77+
log "Dependencies installed successfully."
78+
emit '{"event": "progress", "stage": "build", "message": "Python environment ready"}'
9879

99-
log "Probing for Edge TPU devices..."
100-
emit '{"event": "progress", "stage": "probe", "message": "Checking for Edge TPU devices..."}'
80+
# ─── Step 5: Probe for Edge TPU devices ──────────────────────────────────────
81+
82+
log "Probing for Edge TPU devices natively..."
83+
emit '{"event": "progress", "stage": "probe", "message": "Checking for physical Edge TPU..."}'
10184

10285
TPU_FOUND=false
103-
PROBE_OUTPUT=$("$DOCKER_CMD" run --rm $USB_FLAG \
104-
"$IMAGE_NAME:$IMAGE_TAG" python3 scripts/tpu_probe.py 2>/dev/null) || true
86+
# Run probe inside the venv
87+
PROBE_OUTPUT=$("$VENV_DIR/bin/python" "$SKILL_DIR/scripts/tpu_probe.py" 2>/dev/null) || true
10588

10689
if echo "$PROBE_OUTPUT" | grep -q '"available": true'; then
107-
TPU_COUNT=$(echo "$PROBE_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['count'])" 2>/dev/null || echo "?")
90+
TPU_COUNT=$(echo "$PROBE_OUTPUT" | "$VENV_DIR/bin/python" -c "import sys,json; print(json.load(sys.stdin)['count'])" 2>/dev/null || echo "?")
10891
TPU_FOUND=true
10992
log "Edge TPU detected: $TPU_COUNT device(s)"
110-
emit "{\"event\": \"progress\", \"stage\": \"probe\", \"message\": \"Found $TPU_COUNT Edge TPU device(s)\"}"
93+
emit "{\"event\": \"progress\", \"stage\": \"probe\", \"message\": \"Found $TPU_COUNT Edge TPU device(s) natively\"}"
11194
else
11295
log "WARNING: No Edge TPU detected — skill will run in CPU fallback mode"
11396
emit '{"event": "progress", "stage": "probe", "message": "No Edge TPU detected — CPU fallback available"}'
11497
fi
11598

116-
# ─── Step 5: Complete ────────────────────────────────────────────────────────
117-
11899
# ─── Step 6: Complete ────────────────────────────────────────────────────────
119100

120101
if [ "$TPU_FOUND" = true ]; then
121-
emit "{\"event\": \"complete\", \"status\": \"success\", \"tpu_found\": true, \"message\": \"Coral TPU skill installed — Edge TPU ready\"}"
102+
emit "{\"event\": \"complete\", \"status\": \"success\", \"tpu_found\": true, \"message\": \"Native Coral TPU skill installed — Edge TPU ready\"}"
122103
log "Done! Edge TPU ready."
123104
exit 0
124105
else
125-
emit "{\"event\": \"complete\", \"status\": \"partial\", \"tpu_found\": false, \"message\": \"Coral TPU skill installed — no TPU detected (CPU fallback)\"}"
106+
emit "{\"event\": \"complete\", \"status\": \"partial\", \"tpu_found\": false, \"message\": \"Native Coral TPU skill installed — no TPU detected (CPU fallback)\"}"
126107
log "Done with warning: no TPU detected. Connect Coral USB and restart."
127108
exit 2
128109
fi

skills/detection/yolo-detection-2026-coral-tpu/scripts/monitor.js

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,45 @@
11
/**
2-
* Coral TPU Monitor
3-
* Host-side wrapper to launch the Coral TPU Docker container.
4-
* Matches the CameraClaw architecture by acting as the skill entrypoint.
2+
* Coral TPU Monitor (Native)
3+
* Host-side wrapper to launch the Python detection script directly
4+
* using the natively built virtual environment.
55
*/
66

77
const { spawn } = require('node:child_process');
8+
const path = require('node:path');
89
const os = require('node:os');
9-
const fs = require('node:fs');
1010

1111
function main() {
12-
const imageName = 'aegis-coral-tpu';
13-
const imageTag = 'latest';
14-
15-
const cmd = 'docker';
16-
const args = ['run', '-i', '--rm'];
17-
18-
// Extra PATH augmentation to ensure docker is found when launched via Electron on macOS
19-
const extraPaths = ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin'];
20-
const currentPath = process.env.PATH || '';
21-
const missing = extraPaths.filter(p => !currentPath.split(':').includes(p));
22-
if (missing.length > 0) {
23-
process.env.PATH = [...missing, currentPath].join(':');
24-
}
25-
26-
// Handle USB Passthrough (Coral Edge TPU)
27-
// macOS/Windows handle USB dynamically via Docker Desktop 4.35+
28-
// Only Linux requires explicit device mounting
29-
if (os.platform() === 'linux' && fs.existsSync('/dev/bus/usb')) {
30-
args.push('--device', '/dev/bus/usb:/dev/bus/usb');
31-
}
32-
33-
// Shared memory volume for video frames
34-
const path = require('node:path');
35-
const sharedMemoryHost = path.join(os.tmpdir(), 'aegis-detection-frames');
36-
if (!fs.existsSync(sharedMemoryHost)) {
37-
fs.mkdirSync(sharedMemoryHost, { recursive: true });
12+
const skillRoot = path.join(__dirname, '..');
13+
14+
// Determine Python executable inside the venv
15+
const isWindows = os.platform() === 'win32';
16+
const pythonCmd = isWindows
17+
? path.join(skillRoot, 'venv', 'Scripts', 'python.exe')
18+
: path.join(skillRoot, 'venv', 'bin', 'python3');
19+
20+
const args = [path.join(skillRoot, 'scripts', 'detect.py')];
21+
22+
// We no longer need volume mapping, the python script accesses
23+
// the host's raw /tmp/aegis-detection-frames directories directly!
24+
25+
const env = { ...process.env };
26+
if (!env.PYTHONUNBUFFERED) {
27+
env.PYTHONUNBUFFERED = '1';
3828
}
39-
// Map the host path to the EXACT same absolute path inside the container
40-
// This allows the raw JSON `frame_path` from Aegis to work without translation.
41-
args.push('-v', `${sharedMemoryHost}:${sharedMemoryHost}`);
42-
43-
// Pass through Aegis parameters and ID dynamically
44-
for (const [key, val] of Object.entries(process.env)) {
45-
if (key.startsWith('AEGIS_') || key === 'PYTHONUNBUFFERED') {
46-
args.push('--env', `${key}=${val}`);
47-
}
48-
}
49-
50-
if (!process.env.PYTHONUNBUFFERED) {
51-
args.push('--env', 'PYTHONUNBUFFERED=1');
52-
}
53-
54-
args.push(`${imageName}:${imageTag}`);
5529

56-
const child = spawn(cmd, args, {
57-
stdio: 'inherit'
30+
const child = spawn(pythonCmd, args, {
31+
stdio: 'inherit',
32+
cwd: skillRoot,
33+
env
5834
});
5935

6036
child.on('error', (err) => {
61-
console.log(JSON.stringify({
62-
event: 'error',
63-
message: `Docker is not installed or not in PATH: ${err.message}`,
64-
retriable: false
65-
}));
37+
console.error(`[coral-monitor] Failed to start native python process: ${err.message}`);
6638
process.exit(1);
6739
});
6840

69-
child.on('exit', (code) => {
41+
child.on('exit', (code, signal) => {
42+
console.log(`[coral-monitor] Python process exited with code ${code} (signal ${signal})`);
7043
process.exit(code || 0);
7144
});
7245
}

0 commit comments

Comments
 (0)