infinicode 2.8.36 → 2.8.38
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.
- package/dist/kernel/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +4586 -364
- package/dist/kernel/federation/transport-http.d.ts +2 -0
- package/dist/kernel/federation/transport-http.js +56 -0
- package/dist/robopark/add-robot.d.ts +2 -0
- package/dist/robopark/add-robot.js +9 -0
- package/dist/robopark/secrets.d.ts +8 -0
- package/dist/robopark/secrets.js +41 -0
- package/dist/robopark/setup.d.ts +3 -0
- package/dist/robopark/setup.js +9 -2
- package/dist/robopark-cli.js +22 -0
- package/package.json +105 -105
- package/packages/robopark/scheduler/main.py +1573 -108
- package/packages/robopark/scheduler/preview_agent.py +620 -57
- package/packages/robopark/scheduler/robot_supervisor.py +729 -0
- package/packages/robopark/scheduler/scripts/install-robot-supervisor-linux.sh +33 -0
- package/packages/robopark/scheduler/scripts/install-robot-supervisor-windows.ps1 +49 -0
- package/packages/robopark/scheduler/scripts/robopark-supervisor.service +20 -0
- package/packages/robopark/scheduler/scripts/start-scheduler-local.ps1 +50 -0
- package/packages/robopark/scheduler/supervisor.example.json +26 -0
- package/packages/robopark/scheduler/vision_motion_trigger.py +101 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Installs the RoboPark robot supervisor as a systemd service on a real
|
|
3
|
+
# robot (Raspberry Pi or similar). Auto-starts on boot, restarts on crash.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
SUPERVISOR_DIR="$(dirname "$SCRIPT_DIR")"
|
|
8
|
+
UNIT_SRC="$SCRIPT_DIR/robopark-supervisor.service"
|
|
9
|
+
UNIT_DST="/etc/systemd/system/robopark-supervisor.service"
|
|
10
|
+
|
|
11
|
+
if [ ! -f "$SUPERVISOR_DIR/robot_supervisor.py" ]; then
|
|
12
|
+
echo "robot_supervisor.py not found in $SUPERVISOR_DIR" >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ ! -f "$HOME/.robopark/supervisor.json" ]; then
|
|
17
|
+
echo "No ~/.robopark/supervisor.json yet — copy supervisor.example.json there and edit it first:" >&2
|
|
18
|
+
echo " cp $SUPERVISOR_DIR/supervisor.example.json ~/.robopark/supervisor.json" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo "Installing systemd unit -> $UNIT_DST (requires sudo)"
|
|
23
|
+
sudo sed \
|
|
24
|
+
-e "s#WorkingDirectory=.*#WorkingDirectory=$SUPERVISOR_DIR#" \
|
|
25
|
+
-e "s#User=.*#User=$(whoami)#" \
|
|
26
|
+
"$UNIT_SRC" | sudo tee "$UNIT_DST" > /dev/null
|
|
27
|
+
|
|
28
|
+
sudo systemctl daemon-reload
|
|
29
|
+
sudo systemctl enable robopark-supervisor.service
|
|
30
|
+
sudo systemctl restart robopark-supervisor.service
|
|
31
|
+
|
|
32
|
+
echo "Installed and started. Check status: sudo systemctl status robopark-supervisor.service"
|
|
33
|
+
echo "Logs: journalctl -u robopark-supervisor.service -f (or ~/.robopark/logs/*.log per-service)"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Registers the RoboPark robot supervisor to auto-start at Windows logon.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Creates a Scheduled Task that runs robot_supervisor.py whenever this user
|
|
7
|
+
logs in, restarting it automatically if it ever exits unexpectedly. Runs
|
|
8
|
+
in the interactive session (not "run whether logged on or not") because
|
|
9
|
+
Windows restricts microphone/camera access to interactive sessions —
|
|
10
|
+
preview_agent.py needs both.
|
|
11
|
+
|
|
12
|
+
The supervisor itself (robot_supervisor.py) is what keeps the individual
|
|
13
|
+
robot services (preview_agent, vision app, motor server) alive; this task
|
|
14
|
+
is the one layer above that keeps the supervisor itself running.
|
|
15
|
+
|
|
16
|
+
.PARAMETER PythonExe
|
|
17
|
+
Path to python.exe. Defaults to whatever `python` resolves to on PATH.
|
|
18
|
+
|
|
19
|
+
.EXAMPLE
|
|
20
|
+
.\install-robot-supervisor-windows.ps1
|
|
21
|
+
.\install-robot-supervisor-windows.ps1 -PythonExe "C:\Python312\python.exe"
|
|
22
|
+
#>
|
|
23
|
+
param(
|
|
24
|
+
[string]$PythonExe = (Get-Command python -ErrorAction Stop).Source
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
$ErrorActionPreference = 'Stop'
|
|
28
|
+
$scriptDir = Split-Path -Parent $PSScriptRoot
|
|
29
|
+
$supervisorScript = Join-Path $scriptDir 'robot_supervisor.py'
|
|
30
|
+
if (-not (Test-Path $supervisorScript)) {
|
|
31
|
+
throw "robot_supervisor.py not found at $supervisorScript"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
$taskName = 'RoboParkSupervisor'
|
|
35
|
+
$action = New-ScheduledTaskAction -Execute $PythonExe -Argument "`"$supervisorScript`"" -WorkingDirectory $scriptDir
|
|
36
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
37
|
+
$settings = New-ScheduledTaskSettingsSet `
|
|
38
|
+
-RestartCount 999 `
|
|
39
|
+
-RestartInterval (New-TimeSpan -Minutes 1) `
|
|
40
|
+
-ExecutionTimeLimit (New-TimeSpan -Days 0) `
|
|
41
|
+
-AllowStartIfOnBatteries `
|
|
42
|
+
-DontStopIfGoingOnBatteries
|
|
43
|
+
|
|
44
|
+
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null
|
|
45
|
+
|
|
46
|
+
Write-Output "Registered scheduled task '$taskName' — starts at logon, restarts on crash."
|
|
47
|
+
Write-Output "Config: copy supervisor.example.json to `$HOME\.robopark\supervisor.json and edit it first if you haven't."
|
|
48
|
+
Write-Output "Start it now without logging out: Start-ScheduledTask -TaskName '$taskName'"
|
|
49
|
+
Write-Output "Remove it later: Unregister-ScheduledTask -TaskName '$taskName' -Confirm:`$false"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=RoboPark robot supervisor (preview_agent + vision + motors)
|
|
3
|
+
After=network-online.target sound.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
# Edit these two for your setup:
|
|
9
|
+
User=pi
|
|
10
|
+
WorkingDirectory=/home/pi/infinicode-main/packages/robopark/scheduler
|
|
11
|
+
ExecStart=/usr/bin/python3 robot_supervisor.py
|
|
12
|
+
Restart=always
|
|
13
|
+
RestartSec=5
|
|
14
|
+
# Camera/audio devices, GPIO — adjust group membership instead of running as
|
|
15
|
+
# root: `sudo usermod -aG video,audio,gpio pi`
|
|
16
|
+
StandardOutput=journal
|
|
17
|
+
StandardError=journal
|
|
18
|
+
|
|
19
|
+
[Install]
|
|
20
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Starts the RoboPark scheduler locally with every env var it needs.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
main.py's own auth for the voice agent's session-keepalive calls
|
|
7
|
+
(ROBOPARK_AGENT_TOKEN, checked in POST /api/sessions/{id}/keepalive)
|
|
8
|
+
silently degrades to "every keepalive gets 401'd" if the scheduler is
|
|
9
|
+
ever started without it set -- no startup error, no crash, just every
|
|
10
|
+
motion-triggered session getting cut off by its own silence-timeout
|
|
11
|
+
within ~15s because last_activity_at never updates. This bit twice in
|
|
12
|
+
one session from ad-hoc restarts that forgot the env var. Use this
|
|
13
|
+
script (or set the same vars) for every local restart instead of a bare
|
|
14
|
+
`python main.py`.
|
|
15
|
+
|
|
16
|
+
ROBOPARK_AGENT_TOKEN here MUST match ROBOPARK_AGENT_TOKEN in the voice
|
|
17
|
+
agent's own .env -- it's the shared secret the agent container presents
|
|
18
|
+
via the X-RoboPark-Agent-Token header. Never hardcode the actual value
|
|
19
|
+
in this script (it's committed to git) -- pass it explicitly, set it in
|
|
20
|
+
your shell first, or point -AgentEnvFile at the agent's .env to read it
|
|
21
|
+
from there at run time.
|
|
22
|
+
|
|
23
|
+
.EXAMPLE
|
|
24
|
+
.\start-scheduler-local.ps1 -AgentToken "the-real-token"
|
|
25
|
+
.\start-scheduler-local.ps1 -AgentEnvFile "C:\path\to\ROBOVOICE-main\.env"
|
|
26
|
+
$env:ROBOPARK_AGENT_TOKEN = "the-real-token"; .\start-scheduler-local.ps1
|
|
27
|
+
#>
|
|
28
|
+
param(
|
|
29
|
+
[string]$DataDir = "C:\Users\yunge\OneDrive\Desktop\data",
|
|
30
|
+
[string]$AgentToken = $env:ROBOPARK_AGENT_TOKEN,
|
|
31
|
+
[string]$AgentEnvFile,
|
|
32
|
+
[string]$PythonExe = (Get-Command python -ErrorAction Stop).Source
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
$ErrorActionPreference = 'Stop'
|
|
36
|
+
$schedulerDir = Split-Path -Parent $PSScriptRoot
|
|
37
|
+
|
|
38
|
+
if (-not $AgentToken -and $AgentEnvFile -and (Test-Path $AgentEnvFile)) {
|
|
39
|
+
$line = Get-Content $AgentEnvFile | Where-Object { $_ -match '^ROBOPARK_AGENT_TOKEN=' } | Select-Object -First 1
|
|
40
|
+
if ($line) { $AgentToken = ($line -split '=', 2)[1].Trim() }
|
|
41
|
+
}
|
|
42
|
+
if (-not $AgentToken) {
|
|
43
|
+
throw "ROBOPARK_AGENT_TOKEN not provided. Pass -AgentToken, set `$env:ROBOPARK_AGENT_TOKEN first, or pass -AgentEnvFile pointing at the voice agent's .env."
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
$env:SCHEDULER_DATA_DIR = $DataDir
|
|
47
|
+
$env:ROBOPARK_AGENT_TOKEN = $AgentToken
|
|
48
|
+
|
|
49
|
+
Write-Output "Starting scheduler: SCHEDULER_DATA_DIR=$DataDir, ROBOPARK_AGENT_TOKEN set (len=$($AgentToken.Length))"
|
|
50
|
+
& $PythonExe (Join-Path $schedulerDir 'main.py')
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"log_dir": "~/.robopark/logs",
|
|
3
|
+
"services": [
|
|
4
|
+
{
|
|
5
|
+
"name": "preview_agent",
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"comment": "Core service — heartbeat, LiveKit publish, motion webhook listener. Reads its own settings from ~/.robopark/preview_agent.json (device_id, scheduler_url, etc), enrolled once via `robopark add-robot`.",
|
|
8
|
+
"command": ["python", "preview_agent.py"],
|
|
9
|
+
"cwd": "/absolute/path/to/infinicode-main/packages/robopark/scheduler"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "vision_app",
|
|
13
|
+
"enabled": false,
|
|
14
|
+
"comment": "Real motion/object detection (e.g. RoboVisionAI_PI's app_pi_clean.py). Must POST to preview_agent's motion webhook (http://localhost:<vision-webhook-port>/, default 5057) on detection. Disabled by default — no safe generic command, fill in your robot's actual vision app path.",
|
|
15
|
+
"command": ["python", "app_pi_clean.py"],
|
|
16
|
+
"cwd": "/absolute/path/to/RoboVisionAI_PI"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "motor_server",
|
|
20
|
+
"enabled": false,
|
|
21
|
+
"comment": "Only needed if your robot's motors are NOT wired to preview_agent's LiveKit data-channel path — i.e. voice_agent.py falls back to motor_server_url (set on the character preset / device) instead of the data-channel. Disabled by default.",
|
|
22
|
+
"command": ["python", "-m", "uvicorn", "motors:app", "--host", "0.0.0.0", "--port", "8001"],
|
|
23
|
+
"cwd": "/absolute/path/to/motor-server-project"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Minimal cross-platform motion detector -> preview_agent.py's motion webhook.
|
|
4
|
+
|
|
5
|
+
RoboVisionAI_PI's real app_pi_clean.py does full object detection + motion
|
|
6
|
+
+ MJPEG streaming in one Flask app, but its motion loop only actually runs
|
|
7
|
+
while something is actively pulling /video_feed, and it wants Pi-specific
|
|
8
|
+
caffemodel files for the object-detection half. For a plain "does motion
|
|
9
|
+
actually reach the conversation pipeline" test (or a lightweight always-on
|
|
10
|
+
trigger on a robot that doesn't need the vision overlay), this is a much
|
|
11
|
+
smaller piece: open whatever camera OpenCV can see, do frame-differencing
|
|
12
|
+
motion detection, POST to the webhook on motion, done. No Flask, no model
|
|
13
|
+
files, works the same on Windows/Linux/Pi wherever cv2 can open a camera.
|
|
14
|
+
|
|
15
|
+
preview_agent.py's webhook handler accepts any POST body (even {}) as a
|
|
16
|
+
trigger signal -- it does not require the RoboVisionAI_PI payload shape --
|
|
17
|
+
so this only needs to hit the URL, not match any particular JSON schema.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
python vision_motion_trigger.py --webhook-url http://localhost:5058/
|
|
21
|
+
python vision_motion_trigger.py --camera 0 --min-area 800 --cooldown 15
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import logging
|
|
27
|
+
import time
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger("robopark.vision_motion_trigger")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run(camera_index: int, webhook_url: str, min_area: float, cooldown: float, fps_limit: float) -> None:
|
|
33
|
+
import cv2
|
|
34
|
+
import httpx
|
|
35
|
+
|
|
36
|
+
cap = cv2.VideoCapture(camera_index)
|
|
37
|
+
if not cap.isOpened():
|
|
38
|
+
raise SystemExit(f"could not open camera index {camera_index}")
|
|
39
|
+
logger.info(f"camera {camera_index} opened, watching for motion (min_area={min_area}px, cooldown={cooldown}s)")
|
|
40
|
+
|
|
41
|
+
prev_gray = None
|
|
42
|
+
last_trigger = 0.0
|
|
43
|
+
frame_interval = 1.0 / fps_limit if fps_limit > 0 else 0.0
|
|
44
|
+
client = httpx.Client(timeout=5.0)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
while True:
|
|
48
|
+
loop_start = time.monotonic()
|
|
49
|
+
ok, frame = cap.read()
|
|
50
|
+
if not ok:
|
|
51
|
+
logger.warning("camera read failed, retrying")
|
|
52
|
+
time.sleep(1.0)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
56
|
+
gray = cv2.GaussianBlur(gray, (21, 21), 0)
|
|
57
|
+
|
|
58
|
+
if prev_gray is not None:
|
|
59
|
+
delta = cv2.absdiff(prev_gray, gray)
|
|
60
|
+
thresh = cv2.threshold(delta, 25, 255, cv2.THRESH_BINARY)[1]
|
|
61
|
+
thresh = cv2.dilate(thresh, None, iterations=2)
|
|
62
|
+
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
63
|
+
motion = any(cv2.contourArea(c) >= min_area for c in contours)
|
|
64
|
+
|
|
65
|
+
if motion:
|
|
66
|
+
now = time.time()
|
|
67
|
+
if now - last_trigger >= cooldown:
|
|
68
|
+
last_trigger = now
|
|
69
|
+
logger.info("motion detected, posting to webhook")
|
|
70
|
+
try:
|
|
71
|
+
client.post(webhook_url, json={"source": "vision_motion_trigger"})
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.warning(f"webhook POST failed: {e}")
|
|
74
|
+
else:
|
|
75
|
+
logger.debug("motion detected, still in cooldown")
|
|
76
|
+
|
|
77
|
+
prev_gray = gray
|
|
78
|
+
|
|
79
|
+
if frame_interval:
|
|
80
|
+
elapsed = time.monotonic() - loop_start
|
|
81
|
+
if elapsed < frame_interval:
|
|
82
|
+
time.sleep(frame_interval - elapsed)
|
|
83
|
+
finally:
|
|
84
|
+
cap.release()
|
|
85
|
+
client.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> None:
|
|
89
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
90
|
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
91
|
+
parser.add_argument("--camera", type=int, default=0, help="OpenCV camera index (default 0)")
|
|
92
|
+
parser.add_argument("--webhook-url", default="http://localhost:5058/", help="preview_agent.py's motion webhook")
|
|
93
|
+
parser.add_argument("--min-area", type=float, default=500.0, help="minimum changed-pixel contour area to count as motion")
|
|
94
|
+
parser.add_argument("--cooldown", type=float, default=20.0, help="minimum seconds between triggers")
|
|
95
|
+
parser.add_argument("--fps-limit", type=float, default=5.0, help="cap capture rate to reduce CPU (0 = uncapped)")
|
|
96
|
+
args = parser.parse_args()
|
|
97
|
+
run(args.camera, args.webhook_url, args.min_area, args.cooldown, args.fps_limit)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
main()
|