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.
@@ -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()