robopark 3.2.0 → 3.3.0
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/README.md +66 -105
- package/bin/robopark.js +9 -2
- package/conversation/elevenlabs_agent.py +1617 -1617
- package/conversation/supervisor_store.py +189 -189
- package/dist/{kernel/config-schema.js → config-schema.js} +1 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/robopark/add-robot.js +1 -0
- package/dist/robopark/add-robot.js.map +1 -0
- package/dist/robopark/agent-ctl.js +1 -0
- package/dist/robopark/agent-ctl.js.map +1 -0
- package/dist/robopark/auto-start.js +51 -73
- package/dist/robopark/auto-start.js.map +1 -0
- package/dist/robopark/conversation.js +1 -0
- package/dist/robopark/conversation.js.map +1 -0
- package/dist/robopark/discovery.js +1 -0
- package/dist/robopark/discovery.js.map +1 -0
- package/dist/robopark/doctor.js +1 -0
- package/dist/robopark/doctor.js.map +1 -0
- package/dist/robopark/enroll.js +1 -0
- package/dist/robopark/enroll.js.map +1 -0
- package/dist/robopark/llm-set.js +1 -0
- package/dist/robopark/llm-set.js.map +1 -0
- package/dist/robopark/motor-control.js +1 -0
- package/dist/robopark/motor-control.js.map +1 -0
- package/dist/robopark/preview-agent-launcher.js +1 -0
- package/dist/robopark/preview-agent-launcher.js.map +1 -0
- package/dist/robopark/probe.js +16 -15
- package/dist/robopark/probe.js.map +1 -0
- package/dist/robopark/profile.js +1 -0
- package/dist/robopark/profile.js.map +1 -0
- package/dist/robopark/python-env.js +1 -7
- package/dist/robopark/python-env.js.map +1 -0
- package/dist/robopark/robot-runtime.js +1 -0
- package/dist/robopark/robot-runtime.js.map +1 -0
- package/dist/robopark/scan.js +1 -0
- package/dist/robopark/scan.js.map +1 -0
- package/dist/robopark/screen-control.js +1 -0
- package/dist/robopark/screen-control.js.map +1 -0
- package/dist/robopark/secrets.js +1 -0
- package/dist/robopark/secrets.js.map +1 -0
- package/dist/robopark/serve.js +6 -8
- package/dist/robopark/serve.js.map +1 -0
- package/dist/robopark/server-add.js +1 -0
- package/dist/robopark/server-add.js.map +1 -0
- package/dist/robopark/setup-livekit.js +53 -52
- package/dist/robopark/setup-livekit.js.map +1 -0
- package/dist/robopark/setup.js +1 -0
- package/dist/robopark/setup.js.map +1 -0
- package/dist/robopark/stop-all.js +1 -0
- package/dist/robopark/stop-all.js.map +1 -0
- package/dist/robopark/verify.js +1 -0
- package/dist/robopark/verify.js.map +1 -0
- package/dist/robopark/vision-agent-launcher.js +1 -0
- package/dist/robopark/vision-agent-launcher.js.map +1 -0
- package/dist/robopark/vision-control.js +1 -0
- package/dist/robopark/vision-control.js.map +1 -0
- package/dist/robopark-cli.js +7 -125
- package/dist/robopark-cli.js.map +1 -0
- package/package.json +16 -56
- package/pi-client/_install_steps.sh +29 -29
- package/pi-client/client.py +76 -76
- package/pi-client/install.sh +40 -40
- package/pi-client/join_convo.sh +54 -54
- package/pi-client/livekit_bridge.py +24 -24
- package/pi-client/motor_bridge.py +10 -10
- package/scheduler/__pycache__/main.cpython-312.pyc +0 -0
- package/scheduler/main.py +196 -17
- package/scheduler/media_lock.py +57 -57
- package/scheduler/preview_agent.py +1122 -1122
- package/scheduler/production_config.json +73 -113
- package/scheduler/scripts/install-robot-supervisor-linux.sh +33 -33
- package/{ui/standalone/public → scheduler/static}/voice-join.html +101 -13
- package/screen/screen_runtime.py +75 -75
- package/vision/app_pi_clean.py +297 -297
- package/vision/audio_server_pi.py +24 -24
- package/vision/install.sh +34 -34
- package/vision/motor_server.py +275 -275
- package/vision/requirements_camera.txt +6 -6
- package/vision/requirements_motor.txt +4 -4
- package/vision/requirements_pi_unified.txt +2 -2
- package/vision/requirements_vision_agent.txt +19 -19
- package/vision/run.sh +244 -244
- package/vision/services/services.sh +12 -12
- package/dist/kernel/types.js +0 -7
- package/dist/robopark/access.js +0 -99
- package/dist/robopark/control-center-html.js +0 -9191
- package/dist/robopark/control-server.js +0 -148
- package/dist/robopark/deployment-commands.js +0 -47
- package/dist/robopark/frontend-service.js +0 -80
- package/dist/robopark/standalone.js +0 -584
- package/dist/robopark/voice-join-html.js +0 -57
- package/scripts/windows-shim-fix.cjs +0 -58
- package/static/robopark/barracuda.png +0 -0
- package/static/robopark/bmw.png +0 -0
- package/static/robopark/jaguar.png +0 -0
- package/static/robopark/magnus.jpg +0 -0
- package/static/robopark/panda.png +0 -0
- package/static/robopark/tesla.png +0 -0
- package/static/robopark/titan.jpg +0 -0
- package/static/robopark/volt.png +0 -0
- package/static/vendor/elevenlabs-client.LICENSE +0 -21
- package/static/vendor/elevenlabs-client.iife.js +0 -24200
- package/ui/ROBOVOICE_BUILD.json +0 -9
- package/ui/standalone/.next/BUILD_ID +0 -1
- package/ui/standalone/.next/app-build-manifest.json +0 -391
- package/ui/standalone/.next/app-path-routes-manifest.json +0 -55
- package/ui/standalone/.next/build-manifest.json +0 -33
- package/ui/standalone/.next/package.json +0 -1
- package/ui/standalone/.next/prerender-manifest.json +0 -41
- package/ui/standalone/.next/react-loadable-manifest.json +0 -14
- package/ui/standalone/.next/required-server-files.json +0 -362
- package/ui/standalone/.next/routes-manifest.json +0 -246
- package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route.js +0 -1
- package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/(app)/page.js +0 -2
- package/ui/standalone/.next/server/app/(app)/page.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/(app)/page_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/(app)/voice/page.js +0 -12
- package/ui/standalone/.next/server/app/(app)/voice/page.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/(app)/voice/page_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/_not-found/page.js +0 -12
- package/ui/standalone/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/connection-details/route.js +0 -1
- package/ui/standalone/.next/server/app/api/connection-details/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/connection-details/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/device-character/route.js +0 -1
- package/ui/standalone/.next/server/app/api/device-character/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/device-character/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/download-piper-model/route.js +0 -1
- package/ui/standalone/.next/server/app/api/download-piper-model/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/download-piper-model/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/model-status/route.js +0 -1
- package/ui/standalone/.next/server/app/api/model-status/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/model-status/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/models/route.js +0 -1
- package/ui/standalone/.next/server/app/api/models/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/models/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/prewarm/route.js +0 -1
- package/ui/standalone/.next/server/app/api/prewarm/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/prewarm/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/prompt/route.js +0 -1
- package/ui/standalone/.next/server/app/api/prompt/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/prompt/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/reload-tools/route.js +0 -1
- package/ui/standalone/.next/server/app/api/reload-tools/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/reload-tools/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/camera-token/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/camera-token/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/camera-token/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/character-ai/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/character-ai/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/character-ai/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/device-access/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/device-access/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/device-access/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/live-rooms/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/live-rooms/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/live-rooms/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/monitor-token/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/monitor-token/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/monitor-token/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/robots/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/servers/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/sessions/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/sessions/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/sessions/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/transcripts/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robopark/transcripts/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robopark/transcripts/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route.js +0 -1
- package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/settings/route.js +0 -1
- package/ui/standalone/.next/server/app/api/settings/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/complete/route.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/complete/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/setup/complete/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/status/route.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/status/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/setup/status/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-groq/route.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-groq/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-groq/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-hass/route.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-hass/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-hass/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-n8n/route.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-n8n/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-n8n/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-ollama/route.js +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-ollama/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/setup/test-ollama/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/test-tts/route.js +0 -1
- package/ui/standalone/.next/server/app/api/test-tts/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/test-tts/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/turn-credentials/route.js +0 -1
- package/ui/standalone/.next/server/app/api/turn-credentials/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/turn-credentials/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/voices/route.js +0 -1
- package/ui/standalone/.next/server/app/api/voices/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/voices/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/wake/route.js +0 -1
- package/ui/standalone/.next/server/app/api/wake/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/wake/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/models/route.js +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/models/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/models/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/status/route.js +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/status/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/status/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/upload/route.js +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/upload/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/api/wake-word/upload/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/favicon.ico/route.js +0 -1
- package/ui/standalone/.next/server/app/favicon.ico/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/favicon.ico.body +0 -0
- package/ui/standalone/.next/server/app/favicon.ico.meta +0 -1
- package/ui/standalone/.next/server/app/fed/[...path]/route.js +0 -4
- package/ui/standalone/.next/server/app/fed/[...path]/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/fed/[...path]/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route.js +0 -1
- package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/robopark/[...path]/route.js +0 -1
- package/ui/standalone/.next/server/app/robopark/[...path]/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/robopark/[...path]/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/robopark/api/[...path]/route.js +0 -1
- package/ui/standalone/.next/server/app/robopark/api/[...path]/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/robopark/api/[...path]/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route.js +0 -1
- package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/robopark/page.js +0 -2
- package/ui/standalone/.next/server/app/robopark/page.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/robopark/page_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/tailnet/route.js +0 -1
- package/ui/standalone/.next/server/app/tailnet/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/tailnet/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route.js +0 -1
- package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app/voice-tester/page.js +0 -2
- package/ui/standalone/.next/server/app/voice-tester/page.js.nft.json +0 -1
- package/ui/standalone/.next/server/app/voice-tester/page_client-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/app-paths-manifest.json +0 -55
- package/ui/standalone/.next/server/chunks/1428.js +0 -1
- package/ui/standalone/.next/server/chunks/1692.js +0 -1
- package/ui/standalone/.next/server/chunks/37.js +0 -1
- package/ui/standalone/.next/server/chunks/4876.js +0 -1
- package/ui/standalone/.next/server/chunks/5232.js +0 -9
- package/ui/standalone/.next/server/chunks/5455.js +0 -11
- package/ui/standalone/.next/server/chunks/5611.js +0 -6
- package/ui/standalone/.next/server/chunks/5873.js +0 -22
- package/ui/standalone/.next/server/chunks/6018.js +0 -1
- package/ui/standalone/.next/server/chunks/8755.js +0 -1
- package/ui/standalone/.next/server/chunks/9077.js +0 -11
- package/ui/standalone/.next/server/functions-config-manifest.json +0 -9
- package/ui/standalone/.next/server/middleware-build-manifest.js +0 -1
- package/ui/standalone/.next/server/middleware-manifest.json +0 -6
- package/ui/standalone/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/ui/standalone/.next/server/next-font-manifest.js +0 -1
- package/ui/standalone/.next/server/next-font-manifest.json +0 -1
- package/ui/standalone/.next/server/pages/500.html +0 -1
- package/ui/standalone/.next/server/pages/_app.js +0 -1
- package/ui/standalone/.next/server/pages/_app.js.nft.json +0 -1
- package/ui/standalone/.next/server/pages/_document.js +0 -1
- package/ui/standalone/.next/server/pages/_document.js.nft.json +0 -1
- package/ui/standalone/.next/server/pages/_error.js +0 -19
- package/ui/standalone/.next/server/pages/_error.js.nft.json +0 -1
- package/ui/standalone/.next/server/pages-manifest.json +0 -5
- package/ui/standalone/.next/server/server-reference-manifest.js +0 -1
- package/ui/standalone/.next/server/server-reference-manifest.json +0 -1
- package/ui/standalone/.next/server/webpack-runtime.js +0 -1
- package/ui/standalone/.next/static/T3eGaQUuZNb6rhEStcouc/_buildManifest.js +0 -1
- package/ui/standalone/.next/static/T3eGaQUuZNb6rhEStcouc/_ssgManifest.js +0 -1
- package/ui/standalone/.next/static/chunks/1255-404bb8de49c90395.js +0 -1
- package/ui/standalone/.next/static/chunks/144d3bae-bd5d150c0cab972e.js +0 -1
- package/ui/standalone/.next/static/chunks/1646.9123ee47220ed70b.js +0 -1
- package/ui/standalone/.next/static/chunks/2333-43c39acb531f7d4e.js +0 -3
- package/ui/standalone/.next/static/chunks/4bd1b696-f785427dddbba9fb.js +0 -1
- package/ui/standalone/.next/static/chunks/5139.c5e46d26064a85db.js +0 -1
- package/ui/standalone/.next/static/chunks/76e8f37e-2ae0e3fe7b44d874.js +0 -1
- package/ui/standalone/.next/static/chunks/9880-53262785284464cc.js +0 -1
- package/ui/standalone/.next/static/chunks/adfb71a7-625c1b5ebd039b6a.js +0 -1
- package/ui/standalone/.next/static/chunks/app/(app)/layout-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/(app)/opengraph-image-xg4ifa/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/(app)/page-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/(app)/voice/page-72d72d9a70d9c1d4.js +0 -1
- package/ui/standalone/.next/static/chunks/app/_not-found/page-cfd7daa82d71540c.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/connection-details/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/device-character/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/download-piper-model/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/model-status/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/models/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/prewarm/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/prompt/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/reload-tools/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/camera-token/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/character-ai/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/device-access/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/devices/[deviceId]/control/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/live-rooms/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/monitor-token/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/robots/[robotId]/operations/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/robots/[robotId]/voice-engine/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/robots/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/servers/[serverId]/metrics/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/servers/[serverId]/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/servers/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/sessions/[sessionId]/end/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/sessions/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/transcripts/[sessionId]/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robopark/transcripts/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/robovoice/robots/[robotId]/media-health/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/settings/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/setup/complete/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/setup/status/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/setup/test-groq/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/setup/test-hass/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/setup/test-n8n/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/setup/test-ollama/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/test-tts/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/turn-credentials/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/voices/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/wake/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/wake-word/models/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/wake-word/status/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/api/wake-word/upload/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/fed/[...path]/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/fed/media/robots/[robotId]/video_feed/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/layout-fcce963da1d8220f.js +0 -1
- package/ui/standalone/.next/static/chunks/app/robopark/[...path]/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/robopark/api/[...path]/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/robopark/api/robots/[robotId]/stream/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/robopark/page-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/tailnet/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/vendor/livekit-client.umd.js/route-5f312b128a0caf75.js +0 -1
- package/ui/standalone/.next/static/chunks/app/voice-tester/page-ae699e048e0eb834.js +0 -1
- package/ui/standalone/.next/static/chunks/framework-e60c938074ff7136.js +0 -1
- package/ui/standalone/.next/static/chunks/main-9e24e8708ff075fc.js +0 -1
- package/ui/standalone/.next/static/chunks/main-app-a1f59168303c7f84.js +0 -1
- package/ui/standalone/.next/static/chunks/pages/_app-131c90850aef965b.js +0 -1
- package/ui/standalone/.next/static/chunks/pages/_error-e4ba546eb376bdf4.js +0 -1
- package/ui/standalone/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/ui/standalone/.next/static/chunks/webpack-5252e5254da079b5.js +0 -1
- package/ui/standalone/.next/static/css/3f478a573f9be82e.css +0 -1
- package/ui/standalone/.next/static/css/51560c4af1d5a21f.css +0 -1
- package/ui/standalone/.next/static/media/27fe93087559d480-s.p.otf +0 -0
- package/ui/standalone/.next/static/media/4d3869dd8a56345a-s.p.otf +0 -0
- package/ui/standalone/.next/static/media/63f3bb9ce29e22d5-s.p.otf +0 -0
- package/ui/standalone/.next/static/media/79e1c5d0f2fe9f68-s.p.otf +0 -0
- package/ui/standalone/.next/static/media/8bf7b2ceda89477b-s.p.woff2 +0 -0
- package/ui/standalone/.next/static/media/98baa4a7809fbf72-s.woff2 +0 -0
- package/ui/standalone/.next/static/media/f7650dfaa438c37c-s.woff2 +0 -0
- package/ui/standalone/package.json +0 -57
- package/ui/standalone/public/caal-logo.svg +0 -3
- package/ui/standalone/public/commit-mono-400-regular.woff +0 -0
- package/ui/standalone/public/cwl-logo-round.png +0 -0
- package/ui/standalone/public/everett-light.woff +0 -0
- package/ui/standalone/public/hey_cal.ppn +0 -0
- package/ui/standalone/public/install-robot-kiosk.sh +0 -242
- package/ui/standalone/public/lk-logo-dark.svg +0 -18
- package/ui/standalone/public/lk-logo.svg +0 -18
- package/ui/standalone/public/lk-wordmark.svg +0 -12
- package/ui/standalone/public/opengraph-image-bg.png +0 -0
- package/ui/standalone/public/porcupine_params.pv +0 -0
- package/ui/standalone/public/robopark-control.html +0 -9200
- package/ui/standalone/public/static/robopark/barracuda.png +0 -0
- package/ui/standalone/public/static/robopark/bmw.png +0 -0
- package/ui/standalone/public/static/robopark/jaguar.png +0 -0
- package/ui/standalone/public/static/robopark/magnus.jpg +0 -0
- package/ui/standalone/public/static/robopark/panda.png +0 -0
- package/ui/standalone/public/static/robopark/tesla.png +0 -0
- package/ui/standalone/public/static/robopark/titan.jpg +0 -0
- package/ui/standalone/public/static/robopark/volt.png +0 -0
- package/ui/standalone/public/tailnet/index.html +0 -8995
- package/ui/standalone/public/transcript-analytics.html +0 -72
- package/ui/standalone/public/vendor/livekit-client.umd.js +0 -2
- package/ui/standalone/server.js +0 -38
- /package/{ui/standalone/public → scheduler/static}/vendor/elevenlabs-client.iife.js +0 -0
package/vision/app_pi_clean.py
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
from flask import Flask, Response, jsonify, request
|
|
2
2
|
from flask_cors import CORS
|
|
3
|
-
import argparse
|
|
4
|
-
import glob
|
|
5
|
-
import cv2
|
|
3
|
+
import argparse
|
|
4
|
+
import glob
|
|
5
|
+
import cv2
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
import time
|
|
9
9
|
import numpy as np
|
|
10
|
-
import threading
|
|
11
|
-
import requests
|
|
12
|
-
import base64
|
|
13
|
-
import struct
|
|
10
|
+
import threading
|
|
11
|
+
import requests
|
|
12
|
+
import base64
|
|
13
|
+
import struct
|
|
14
14
|
|
|
15
15
|
app = Flask(__name__)
|
|
16
16
|
CORS(app)
|
|
17
17
|
|
|
18
|
-
# Camera management. Production installs create /dev/robopark-camera from
|
|
19
|
-
# the primary USB capture interface, avoiding /dev/videoN renumbering.
|
|
20
|
-
def _normalize_camera_device(value):
|
|
21
|
-
configured = str(value or "").strip()
|
|
22
|
-
if configured.lower() in ("", "auto", "default", "first"):
|
|
23
|
-
if sys.platform.startswith("linux"):
|
|
24
|
-
return "/dev/robopark-camera" if os.path.exists("/dev/robopark-camera") else "/dev/video0"
|
|
25
|
-
return 0
|
|
26
|
-
if configured.isdigit():
|
|
27
|
-
return int(configured)
|
|
28
|
-
return configured
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
_configured_camera = os.getenv("ROBOPARK_CAMERA_DEVICE", "")
|
|
32
|
-
current_camera_index = _normalize_camera_device(_configured_camera)
|
|
33
|
-
camera = None
|
|
34
|
-
camera_lock = threading.Lock()
|
|
35
|
-
frame_condition = threading.Condition()
|
|
36
|
-
latest_frame_bytes = None
|
|
37
|
-
latest_frame_sequence = 0
|
|
38
|
-
camera_worker_started = False
|
|
39
|
-
camera_read_started_at = 0.0
|
|
40
|
-
camera_last_frame_at = 0.0
|
|
41
|
-
audio_input_device = "default"
|
|
42
|
-
audio_output_device = "default"
|
|
43
|
-
ROBOVISION_AUDIO_URL = os.getenv("ROBOVISION_AUDIO_URL", "http://127.0.0.1:8000")
|
|
44
|
-
_camera_inventory_cache = []
|
|
45
|
-
_camera_inventory_cache_at = 0.0
|
|
46
|
-
CAMERA_INVENTORY_CACHE_SECONDS = 30.0
|
|
18
|
+
# Camera management. Production installs create /dev/robopark-camera from
|
|
19
|
+
# the primary USB capture interface, avoiding /dev/videoN renumbering.
|
|
20
|
+
def _normalize_camera_device(value):
|
|
21
|
+
configured = str(value or "").strip()
|
|
22
|
+
if configured.lower() in ("", "auto", "default", "first"):
|
|
23
|
+
if sys.platform.startswith("linux"):
|
|
24
|
+
return "/dev/robopark-camera" if os.path.exists("/dev/robopark-camera") else "/dev/video0"
|
|
25
|
+
return 0
|
|
26
|
+
if configured.isdigit():
|
|
27
|
+
return int(configured)
|
|
28
|
+
return configured
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_configured_camera = os.getenv("ROBOPARK_CAMERA_DEVICE", "")
|
|
32
|
+
current_camera_index = _normalize_camera_device(_configured_camera)
|
|
33
|
+
camera = None
|
|
34
|
+
camera_lock = threading.Lock()
|
|
35
|
+
frame_condition = threading.Condition()
|
|
36
|
+
latest_frame_bytes = None
|
|
37
|
+
latest_frame_sequence = 0
|
|
38
|
+
camera_worker_started = False
|
|
39
|
+
camera_read_started_at = 0.0
|
|
40
|
+
camera_last_frame_at = 0.0
|
|
41
|
+
audio_input_device = "default"
|
|
42
|
+
audio_output_device = "default"
|
|
43
|
+
ROBOVISION_AUDIO_URL = os.getenv("ROBOVISION_AUDIO_URL", "http://127.0.0.1:8000")
|
|
44
|
+
_camera_inventory_cache = []
|
|
45
|
+
_camera_inventory_cache_at = 0.0
|
|
46
|
+
CAMERA_INVENTORY_CACHE_SECONDS = 30.0
|
|
47
47
|
|
|
48
48
|
def _open_camera(index):
|
|
49
49
|
# On Windows, cv2.VideoCapture(index) with no explicit backend can
|
|
@@ -52,17 +52,17 @@ def _open_camera(index):
|
|
|
52
52
|
# webcam — isOpened() then stays False forever and callers retry in an
|
|
53
53
|
# infinite doomed loop. Force DirectShow, the backend that actually
|
|
54
54
|
# enumerates standard Windows webcams.
|
|
55
|
-
if sys.platform == 'win32':
|
|
56
|
-
return cv2.VideoCapture(index, cv2.CAP_DSHOW)
|
|
57
|
-
if sys.platform.startswith('linux'):
|
|
58
|
-
cap = cv2.VideoCapture(index, cv2.CAP_V4L2)
|
|
59
|
-
if cap.isOpened():
|
|
60
|
-
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
|
|
61
|
-
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
|
|
62
|
-
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
|
|
63
|
-
cap.set(cv2.CAP_PROP_FPS, 15)
|
|
64
|
-
return cap
|
|
65
|
-
return cv2.VideoCapture(index)
|
|
55
|
+
if sys.platform == 'win32':
|
|
56
|
+
return cv2.VideoCapture(index, cv2.CAP_DSHOW)
|
|
57
|
+
if sys.platform.startswith('linux'):
|
|
58
|
+
cap = cv2.VideoCapture(index, cv2.CAP_V4L2)
|
|
59
|
+
if cap.isOpened():
|
|
60
|
+
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
|
|
61
|
+
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
|
|
62
|
+
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
|
|
63
|
+
cap.set(cv2.CAP_PROP_FPS, 15)
|
|
64
|
+
return cap
|
|
65
|
+
return cv2.VideoCapture(index)
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def get_camera():
|
|
@@ -252,39 +252,39 @@ def send_webhook(frame_data):
|
|
|
252
252
|
except Exception as e:
|
|
253
253
|
print(f"Error preparing webhook: {e}")
|
|
254
254
|
|
|
255
|
-
def camera_worker():
|
|
256
|
-
global latest_detections, motion_detection_active, motion_detected_state
|
|
257
|
-
global last_motion_time, motion_frame_buffer
|
|
258
|
-
global latest_frame_bytes, latest_frame_sequence
|
|
259
|
-
global camera_read_started_at, camera_last_frame_at, camera
|
|
260
|
-
|
|
261
|
-
prev_gray = None
|
|
262
|
-
|
|
263
|
-
while True:
|
|
264
|
-
with camera_lock:
|
|
265
|
-
cam = get_camera()
|
|
266
|
-
if cam is None or not cam.isOpened():
|
|
267
|
-
time.sleep(1)
|
|
268
|
-
continue
|
|
269
|
-
|
|
270
|
-
camera_read_started_at = time.monotonic()
|
|
271
|
-
try:
|
|
272
|
-
# This is the only camera reader. Do not hold camera_lock here:
|
|
273
|
-
# the watchdog must be able to release a wedged V4L2 handle.
|
|
274
|
-
success, frame = cam.read()
|
|
275
|
-
except Exception as exc:
|
|
276
|
-
print(f"Camera read error: {exc}")
|
|
277
|
-
success, frame = False, None
|
|
278
|
-
finally:
|
|
279
|
-
camera_read_started_at = 0.0
|
|
280
|
-
if not success:
|
|
281
|
-
with camera_lock:
|
|
282
|
-
if camera is cam:
|
|
283
|
-
camera.release()
|
|
284
|
-
camera = None
|
|
285
|
-
time.sleep(0.1)
|
|
286
|
-
continue
|
|
287
|
-
camera_last_frame_at = time.monotonic()
|
|
255
|
+
def camera_worker():
|
|
256
|
+
global latest_detections, motion_detection_active, motion_detected_state
|
|
257
|
+
global last_motion_time, motion_frame_buffer
|
|
258
|
+
global latest_frame_bytes, latest_frame_sequence
|
|
259
|
+
global camera_read_started_at, camera_last_frame_at, camera
|
|
260
|
+
|
|
261
|
+
prev_gray = None
|
|
262
|
+
|
|
263
|
+
while True:
|
|
264
|
+
with camera_lock:
|
|
265
|
+
cam = get_camera()
|
|
266
|
+
if cam is None or not cam.isOpened():
|
|
267
|
+
time.sleep(1)
|
|
268
|
+
continue
|
|
269
|
+
|
|
270
|
+
camera_read_started_at = time.monotonic()
|
|
271
|
+
try:
|
|
272
|
+
# This is the only camera reader. Do not hold camera_lock here:
|
|
273
|
+
# the watchdog must be able to release a wedged V4L2 handle.
|
|
274
|
+
success, frame = cam.read()
|
|
275
|
+
except Exception as exc:
|
|
276
|
+
print(f"Camera read error: {exc}")
|
|
277
|
+
success, frame = False, None
|
|
278
|
+
finally:
|
|
279
|
+
camera_read_started_at = 0.0
|
|
280
|
+
if not success:
|
|
281
|
+
with camera_lock:
|
|
282
|
+
if camera is cam:
|
|
283
|
+
camera.release()
|
|
284
|
+
camera = None
|
|
285
|
+
time.sleep(0.1)
|
|
286
|
+
continue
|
|
287
|
+
camera_last_frame_at = time.monotonic()
|
|
288
288
|
|
|
289
289
|
# Run object detection
|
|
290
290
|
processed_frame, detections = detect_objects(frame, conf_threshold=0.5)
|
|
@@ -333,59 +333,59 @@ def camera_worker():
|
|
|
333
333
|
|
|
334
334
|
prev_gray = gray
|
|
335
335
|
|
|
336
|
-
ret, buffer = cv2.imencode('.jpg', processed_frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
|
|
337
|
-
if not ret:
|
|
338
|
-
continue
|
|
339
|
-
with frame_condition:
|
|
340
|
-
latest_frame_bytes = buffer.tobytes()
|
|
341
|
-
latest_frame_sequence += 1
|
|
342
|
-
frame_condition.notify_all()
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
def _ensure_camera_worker():
|
|
346
|
-
global camera_worker_started
|
|
347
|
-
with frame_condition:
|
|
348
|
-
if camera_worker_started:
|
|
349
|
-
return
|
|
350
|
-
camera_worker_started = True
|
|
351
|
-
threading.Thread(target=camera_worker, name='robovision-camera', daemon=True).start()
|
|
352
|
-
threading.Thread(target=_camera_watchdog, name='robovision-camera-watchdog', daemon=True).start()
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _camera_watchdog():
|
|
356
|
-
global camera, camera_read_started_at
|
|
357
|
-
while True:
|
|
358
|
-
time.sleep(2.0)
|
|
359
|
-
started = camera_read_started_at
|
|
360
|
-
if not started or time.monotonic() - started < 8.0:
|
|
361
|
-
continue
|
|
362
|
-
print("Camera read stalled for 8s; releasing V4L2 handle")
|
|
363
|
-
with camera_lock:
|
|
364
|
-
if camera is not None:
|
|
365
|
-
try:
|
|
366
|
-
camera.release()
|
|
367
|
-
except Exception:
|
|
368
|
-
pass
|
|
369
|
-
camera = None
|
|
370
|
-
camera_read_started_at = 0.0
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
def generate_frames():
|
|
374
|
-
_ensure_camera_worker()
|
|
375
|
-
sequence = -1
|
|
376
|
-
while True:
|
|
377
|
-
with frame_condition:
|
|
378
|
-
frame_condition.wait_for(
|
|
379
|
-
lambda: latest_frame_bytes is not None and latest_frame_sequence != sequence,
|
|
380
|
-
timeout=5.0,
|
|
381
|
-
)
|
|
382
|
-
if latest_frame_bytes is None or latest_frame_sequence == sequence:
|
|
383
|
-
continue
|
|
384
|
-
frame_bytes = latest_frame_bytes
|
|
385
|
-
sequence = latest_frame_sequence
|
|
386
|
-
|
|
387
|
-
yield (b'--frame\r\n'
|
|
388
|
-
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
|
|
336
|
+
ret, buffer = cv2.imencode('.jpg', processed_frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
|
|
337
|
+
if not ret:
|
|
338
|
+
continue
|
|
339
|
+
with frame_condition:
|
|
340
|
+
latest_frame_bytes = buffer.tobytes()
|
|
341
|
+
latest_frame_sequence += 1
|
|
342
|
+
frame_condition.notify_all()
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _ensure_camera_worker():
|
|
346
|
+
global camera_worker_started
|
|
347
|
+
with frame_condition:
|
|
348
|
+
if camera_worker_started:
|
|
349
|
+
return
|
|
350
|
+
camera_worker_started = True
|
|
351
|
+
threading.Thread(target=camera_worker, name='robovision-camera', daemon=True).start()
|
|
352
|
+
threading.Thread(target=_camera_watchdog, name='robovision-camera-watchdog', daemon=True).start()
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _camera_watchdog():
|
|
356
|
+
global camera, camera_read_started_at
|
|
357
|
+
while True:
|
|
358
|
+
time.sleep(2.0)
|
|
359
|
+
started = camera_read_started_at
|
|
360
|
+
if not started or time.monotonic() - started < 8.0:
|
|
361
|
+
continue
|
|
362
|
+
print("Camera read stalled for 8s; releasing V4L2 handle")
|
|
363
|
+
with camera_lock:
|
|
364
|
+
if camera is not None:
|
|
365
|
+
try:
|
|
366
|
+
camera.release()
|
|
367
|
+
except Exception:
|
|
368
|
+
pass
|
|
369
|
+
camera = None
|
|
370
|
+
camera_read_started_at = 0.0
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def generate_frames():
|
|
374
|
+
_ensure_camera_worker()
|
|
375
|
+
sequence = -1
|
|
376
|
+
while True:
|
|
377
|
+
with frame_condition:
|
|
378
|
+
frame_condition.wait_for(
|
|
379
|
+
lambda: latest_frame_bytes is not None and latest_frame_sequence != sequence,
|
|
380
|
+
timeout=5.0,
|
|
381
|
+
)
|
|
382
|
+
if latest_frame_bytes is None or latest_frame_sequence == sequence:
|
|
383
|
+
continue
|
|
384
|
+
frame_bytes = latest_frame_bytes
|
|
385
|
+
sequence = latest_frame_sequence
|
|
386
|
+
|
|
387
|
+
yield (b'--frame\r\n'
|
|
388
|
+
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
|
|
389
389
|
|
|
390
390
|
@app.route('/')
|
|
391
391
|
def index():
|
|
@@ -395,23 +395,23 @@ def index():
|
|
|
395
395
|
def video_feed():
|
|
396
396
|
return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
|
397
397
|
|
|
398
|
-
@app.route('/api/detections')
|
|
399
|
-
def get_detections():
|
|
400
|
-
with lock:
|
|
401
|
-
return jsonify(latest_detections)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
@app.route('/api/camera/status')
|
|
405
|
-
def camera_status():
|
|
406
|
-
age = None if not camera_last_frame_at else round(time.monotonic() - camera_last_frame_at, 3)
|
|
407
|
-
return jsonify({
|
|
408
|
-
"device": str(current_camera_index),
|
|
409
|
-
"open": bool(camera is not None and camera.isOpened()),
|
|
410
|
-
"worker_started": camera_worker_started,
|
|
411
|
-
"frame_sequence": latest_frame_sequence,
|
|
412
|
-
"last_frame_age_seconds": age,
|
|
413
|
-
"read_stalled": bool(camera_read_started_at and time.monotonic() - camera_read_started_at >= 8.0),
|
|
414
|
-
})
|
|
398
|
+
@app.route('/api/detections')
|
|
399
|
+
def get_detections():
|
|
400
|
+
with lock:
|
|
401
|
+
return jsonify(latest_detections)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@app.route('/api/camera/status')
|
|
405
|
+
def camera_status():
|
|
406
|
+
age = None if not camera_last_frame_at else round(time.monotonic() - camera_last_frame_at, 3)
|
|
407
|
+
return jsonify({
|
|
408
|
+
"device": str(current_camera_index),
|
|
409
|
+
"open": bool(camera is not None and camera.isOpened()),
|
|
410
|
+
"worker_started": camera_worker_started,
|
|
411
|
+
"frame_sequence": latest_frame_sequence,
|
|
412
|
+
"last_frame_age_seconds": age,
|
|
413
|
+
"read_stalled": bool(camera_read_started_at and time.monotonic() - camera_read_started_at >= 8.0),
|
|
414
|
+
})
|
|
415
415
|
|
|
416
416
|
@app.route('/api/caption')
|
|
417
417
|
def get_caption():
|
|
@@ -426,149 +426,149 @@ def caption_mode():
|
|
|
426
426
|
caption_mode_enabled = bool(data.get('enabled', False))
|
|
427
427
|
return jsonify({"enabled": caption_mode_enabled})
|
|
428
428
|
|
|
429
|
-
@app.route('/api/cameras', methods=['GET'])
|
|
430
|
-
def list_cameras():
|
|
431
|
-
global _camera_inventory_cache, _camera_inventory_cache_at
|
|
432
|
-
now = time.monotonic()
|
|
433
|
-
if now - _camera_inventory_cache_at < CAMERA_INVENTORY_CACHE_SECONDS:
|
|
434
|
-
return jsonify({"cameras": _camera_inventory_cache, "current": current_camera_index})
|
|
435
|
-
|
|
436
|
-
available = []
|
|
437
|
-
if sys.platform.startswith('linux'):
|
|
438
|
-
# Query capabilities without starting a stream. Opening every V4L2
|
|
439
|
-
# node through OpenCV also opens metadata/output nodes and can contend
|
|
440
|
-
# with the camera stream already owned by RoboVision.
|
|
441
|
-
import fcntl
|
|
442
|
-
vidioc_querycap = 0x80685600
|
|
443
|
-
video_capture = 0x00000001
|
|
444
|
-
video_capture_mplane = 0x00001000
|
|
445
|
-
device_caps_flag = 0x80000000
|
|
446
|
-
for candidate in sorted(glob.glob('/dev/video*')):
|
|
447
|
-
fd = None
|
|
448
|
-
try:
|
|
449
|
-
fd = os.open(candidate, os.O_RDONLY | os.O_NONBLOCK)
|
|
450
|
-
capability = bytearray(104)
|
|
451
|
-
fcntl.ioctl(fd, vidioc_querycap, capability, True)
|
|
452
|
-
capabilities = struct.unpack_from('=I', capability, 84)[0]
|
|
453
|
-
device_caps = struct.unpack_from('=I', capability, 88)[0]
|
|
454
|
-
effective = device_caps if capabilities & device_caps_flag else capabilities
|
|
455
|
-
if not effective & (video_capture | video_capture_mplane):
|
|
456
|
-
continue
|
|
457
|
-
card = bytes(capability[16:48]).split(b'\0', 1)[0].decode('utf-8', 'replace')
|
|
458
|
-
available.append({
|
|
459
|
-
"index": candidate,
|
|
460
|
-
"id": candidate,
|
|
461
|
-
"name": card or f"Camera {candidate}",
|
|
462
|
-
"backend": "v4l2",
|
|
463
|
-
})
|
|
464
|
-
except (OSError, ValueError):
|
|
465
|
-
continue
|
|
466
|
-
finally:
|
|
467
|
-
if fd is not None:
|
|
468
|
-
os.close(fd)
|
|
469
|
-
else:
|
|
470
|
-
for candidate in range(4):
|
|
471
|
-
cap = _open_camera(candidate)
|
|
472
|
-
if cap.isOpened():
|
|
473
|
-
available.append({"index": candidate, "id": str(candidate), "name": f"Camera {candidate}", "backend": "dshow"})
|
|
474
|
-
cap.release()
|
|
475
|
-
|
|
476
|
-
_camera_inventory_cache = available
|
|
477
|
-
_camera_inventory_cache_at = now
|
|
478
|
-
return jsonify({"cameras": available, "current": current_camera_index})
|
|
429
|
+
@app.route('/api/cameras', methods=['GET'])
|
|
430
|
+
def list_cameras():
|
|
431
|
+
global _camera_inventory_cache, _camera_inventory_cache_at
|
|
432
|
+
now = time.monotonic()
|
|
433
|
+
if now - _camera_inventory_cache_at < CAMERA_INVENTORY_CACHE_SECONDS:
|
|
434
|
+
return jsonify({"cameras": _camera_inventory_cache, "current": current_camera_index})
|
|
435
|
+
|
|
436
|
+
available = []
|
|
437
|
+
if sys.platform.startswith('linux'):
|
|
438
|
+
# Query capabilities without starting a stream. Opening every V4L2
|
|
439
|
+
# node through OpenCV also opens metadata/output nodes and can contend
|
|
440
|
+
# with the camera stream already owned by RoboVision.
|
|
441
|
+
import fcntl
|
|
442
|
+
vidioc_querycap = 0x80685600
|
|
443
|
+
video_capture = 0x00000001
|
|
444
|
+
video_capture_mplane = 0x00001000
|
|
445
|
+
device_caps_flag = 0x80000000
|
|
446
|
+
for candidate in sorted(glob.glob('/dev/video*')):
|
|
447
|
+
fd = None
|
|
448
|
+
try:
|
|
449
|
+
fd = os.open(candidate, os.O_RDONLY | os.O_NONBLOCK)
|
|
450
|
+
capability = bytearray(104)
|
|
451
|
+
fcntl.ioctl(fd, vidioc_querycap, capability, True)
|
|
452
|
+
capabilities = struct.unpack_from('=I', capability, 84)[0]
|
|
453
|
+
device_caps = struct.unpack_from('=I', capability, 88)[0]
|
|
454
|
+
effective = device_caps if capabilities & device_caps_flag else capabilities
|
|
455
|
+
if not effective & (video_capture | video_capture_mplane):
|
|
456
|
+
continue
|
|
457
|
+
card = bytes(capability[16:48]).split(b'\0', 1)[0].decode('utf-8', 'replace')
|
|
458
|
+
available.append({
|
|
459
|
+
"index": candidate,
|
|
460
|
+
"id": candidate,
|
|
461
|
+
"name": card or f"Camera {candidate}",
|
|
462
|
+
"backend": "v4l2",
|
|
463
|
+
})
|
|
464
|
+
except (OSError, ValueError):
|
|
465
|
+
continue
|
|
466
|
+
finally:
|
|
467
|
+
if fd is not None:
|
|
468
|
+
os.close(fd)
|
|
469
|
+
else:
|
|
470
|
+
for candidate in range(4):
|
|
471
|
+
cap = _open_camera(candidate)
|
|
472
|
+
if cap.isOpened():
|
|
473
|
+
available.append({"index": candidate, "id": str(candidate), "name": f"Camera {candidate}", "backend": "dshow"})
|
|
474
|
+
cap.release()
|
|
475
|
+
|
|
476
|
+
_camera_inventory_cache = available
|
|
477
|
+
_camera_inventory_cache_at = now
|
|
478
|
+
return jsonify({"cameras": available, "current": current_camera_index})
|
|
479
479
|
|
|
480
480
|
@app.route('/api/camera/switch', methods=['POST'])
|
|
481
|
-
def switch_camera():
|
|
481
|
+
def switch_camera():
|
|
482
482
|
global camera, current_camera_index
|
|
483
483
|
data = request.json or {}
|
|
484
|
-
new_index = data.get('device', data.get('index', 0))
|
|
485
|
-
if isinstance(new_index, str) and new_index.isdigit():
|
|
486
|
-
new_index = int(new_index)
|
|
487
|
-
|
|
488
|
-
with camera_lock:
|
|
489
|
-
if camera:
|
|
490
|
-
camera.release()
|
|
491
|
-
current_camera_index = _normalize_camera_device(new_index)
|
|
492
|
-
camera = None
|
|
493
|
-
|
|
494
|
-
return jsonify({"status": "ok", "camera_index": current_camera_index})
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
def _audio_inventory():
|
|
498
|
-
"""Adapt RoboVision's existing audio_server_pi /devices response."""
|
|
499
|
-
try:
|
|
500
|
-
response = requests.get(f"{ROBOVISION_AUDIO_URL.rstrip('/')}/devices", timeout=0.8)
|
|
501
|
-
response.raise_for_status()
|
|
502
|
-
payload = response.json()
|
|
503
|
-
inputs, outputs = [], []
|
|
504
|
-
for device in payload.get("devices", []):
|
|
505
|
-
item = {
|
|
506
|
-
"id": str(device["index"]),
|
|
507
|
-
"name": str(device.get("name", f"Audio device {device['index']}")),
|
|
508
|
-
"backend": "robovision_audio",
|
|
509
|
-
"sample_rate": device.get("default_samplerate"),
|
|
510
|
-
}
|
|
511
|
-
if device.get("max_input_channels", 0) > 0:
|
|
512
|
-
inputs.append(item.copy())
|
|
513
|
-
if device.get("max_output_channels", 0) > 0:
|
|
514
|
-
outputs.append(item.copy())
|
|
515
|
-
return inputs, outputs, {
|
|
516
|
-
"input": payload.get("bluetooth_input"),
|
|
517
|
-
"output": payload.get("bluetooth_output"),
|
|
518
|
-
"online": True,
|
|
519
|
-
}
|
|
520
|
-
except Exception as exc:
|
|
521
|
-
return [], [], {"online": False, "error": str(exc)}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
@app.route('/api/media/inventory', methods=['GET'])
|
|
525
|
-
def media_inventory():
|
|
526
|
-
cameras = list_cameras().get_json().get("cameras", [])
|
|
527
|
-
inputs, outputs, audio_state = _audio_inventory()
|
|
528
|
-
return jsonify({
|
|
529
|
-
"video": [{"id": "auto", "name": "Auto detect"}, {"id": "none", "name": "Disable camera"}] + cameras,
|
|
530
|
-
"audio_input": inputs,
|
|
531
|
-
"audio_output": outputs,
|
|
532
|
-
"selected": {
|
|
533
|
-
"video_device": str(current_camera_index),
|
|
534
|
-
"audio_device": str(audio_state.get("input") if audio_state.get("input") is not None else audio_input_device),
|
|
535
|
-
"audio_output_device": str(audio_state.get("output") if audio_state.get("output") is not None else audio_output_device),
|
|
536
|
-
},
|
|
537
|
-
"audio_server": audio_state,
|
|
538
|
-
"source": "robovision_pi",
|
|
539
|
-
})
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
@app.route('/api/media/config', methods=['GET', 'POST'])
|
|
543
|
-
def media_config():
|
|
544
|
-
global audio_input_device, audio_output_device, camera, current_camera_index
|
|
545
|
-
if request.method == 'POST':
|
|
546
|
-
data = request.json or {}
|
|
547
|
-
if "video_device" in data:
|
|
548
|
-
value = str(data["video_device"])
|
|
549
|
-
if value not in ("", "auto", "none"):
|
|
550
|
-
switch_camera_value = value
|
|
551
|
-
if camera:
|
|
552
|
-
camera.release()
|
|
553
|
-
current_camera_index = _normalize_camera_device(switch_camera_value)
|
|
554
|
-
camera = None
|
|
555
|
-
if "audio_device" in data:
|
|
556
|
-
audio_input_device = str(data["audio_device"])
|
|
557
|
-
if "audio_output_device" in data:
|
|
558
|
-
audio_output_device = str(data["audio_output_device"])
|
|
559
|
-
if "audio_device" in data or "audio_output_device" in data:
|
|
560
|
-
# audio_server_pi.py is RoboVision's authoritative selector.
|
|
561
|
-
# It accepts its original sounddevice indices via input/output.
|
|
562
|
-
payload = {}
|
|
563
|
-
if "audio_device" in data:
|
|
564
|
-
payload["input"] = int(audio_input_device) if audio_input_device.isdigit() else audio_input_device
|
|
565
|
-
if "audio_output_device" in data:
|
|
566
|
-
payload["output"] = int(audio_output_device) if audio_output_device.isdigit() else audio_output_device
|
|
567
|
-
try:
|
|
568
|
-
requests.post(f"{ROBOVISION_AUDIO_URL.rstrip('/')}/set-device", json=payload, timeout=0.8).raise_for_status()
|
|
569
|
-
except Exception:
|
|
570
|
-
pass
|
|
571
|
-
return jsonify({"video_device": str(current_camera_index), "audio_device": audio_input_device, "audio_output_device": audio_output_device, "source": "robovision_pi"})
|
|
484
|
+
new_index = data.get('device', data.get('index', 0))
|
|
485
|
+
if isinstance(new_index, str) and new_index.isdigit():
|
|
486
|
+
new_index = int(new_index)
|
|
487
|
+
|
|
488
|
+
with camera_lock:
|
|
489
|
+
if camera:
|
|
490
|
+
camera.release()
|
|
491
|
+
current_camera_index = _normalize_camera_device(new_index)
|
|
492
|
+
camera = None
|
|
493
|
+
|
|
494
|
+
return jsonify({"status": "ok", "camera_index": current_camera_index})
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _audio_inventory():
|
|
498
|
+
"""Adapt RoboVision's existing audio_server_pi /devices response."""
|
|
499
|
+
try:
|
|
500
|
+
response = requests.get(f"{ROBOVISION_AUDIO_URL.rstrip('/')}/devices", timeout=0.8)
|
|
501
|
+
response.raise_for_status()
|
|
502
|
+
payload = response.json()
|
|
503
|
+
inputs, outputs = [], []
|
|
504
|
+
for device in payload.get("devices", []):
|
|
505
|
+
item = {
|
|
506
|
+
"id": str(device["index"]),
|
|
507
|
+
"name": str(device.get("name", f"Audio device {device['index']}")),
|
|
508
|
+
"backend": "robovision_audio",
|
|
509
|
+
"sample_rate": device.get("default_samplerate"),
|
|
510
|
+
}
|
|
511
|
+
if device.get("max_input_channels", 0) > 0:
|
|
512
|
+
inputs.append(item.copy())
|
|
513
|
+
if device.get("max_output_channels", 0) > 0:
|
|
514
|
+
outputs.append(item.copy())
|
|
515
|
+
return inputs, outputs, {
|
|
516
|
+
"input": payload.get("bluetooth_input"),
|
|
517
|
+
"output": payload.get("bluetooth_output"),
|
|
518
|
+
"online": True,
|
|
519
|
+
}
|
|
520
|
+
except Exception as exc:
|
|
521
|
+
return [], [], {"online": False, "error": str(exc)}
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@app.route('/api/media/inventory', methods=['GET'])
|
|
525
|
+
def media_inventory():
|
|
526
|
+
cameras = list_cameras().get_json().get("cameras", [])
|
|
527
|
+
inputs, outputs, audio_state = _audio_inventory()
|
|
528
|
+
return jsonify({
|
|
529
|
+
"video": [{"id": "auto", "name": "Auto detect"}, {"id": "none", "name": "Disable camera"}] + cameras,
|
|
530
|
+
"audio_input": inputs,
|
|
531
|
+
"audio_output": outputs,
|
|
532
|
+
"selected": {
|
|
533
|
+
"video_device": str(current_camera_index),
|
|
534
|
+
"audio_device": str(audio_state.get("input") if audio_state.get("input") is not None else audio_input_device),
|
|
535
|
+
"audio_output_device": str(audio_state.get("output") if audio_state.get("output") is not None else audio_output_device),
|
|
536
|
+
},
|
|
537
|
+
"audio_server": audio_state,
|
|
538
|
+
"source": "robovision_pi",
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@app.route('/api/media/config', methods=['GET', 'POST'])
|
|
543
|
+
def media_config():
|
|
544
|
+
global audio_input_device, audio_output_device, camera, current_camera_index
|
|
545
|
+
if request.method == 'POST':
|
|
546
|
+
data = request.json or {}
|
|
547
|
+
if "video_device" in data:
|
|
548
|
+
value = str(data["video_device"])
|
|
549
|
+
if value not in ("", "auto", "none"):
|
|
550
|
+
switch_camera_value = value
|
|
551
|
+
if camera:
|
|
552
|
+
camera.release()
|
|
553
|
+
current_camera_index = _normalize_camera_device(switch_camera_value)
|
|
554
|
+
camera = None
|
|
555
|
+
if "audio_device" in data:
|
|
556
|
+
audio_input_device = str(data["audio_device"])
|
|
557
|
+
if "audio_output_device" in data:
|
|
558
|
+
audio_output_device = str(data["audio_output_device"])
|
|
559
|
+
if "audio_device" in data or "audio_output_device" in data:
|
|
560
|
+
# audio_server_pi.py is RoboVision's authoritative selector.
|
|
561
|
+
# It accepts its original sounddevice indices via input/output.
|
|
562
|
+
payload = {}
|
|
563
|
+
if "audio_device" in data:
|
|
564
|
+
payload["input"] = int(audio_input_device) if audio_input_device.isdigit() else audio_input_device
|
|
565
|
+
if "audio_output_device" in data:
|
|
566
|
+
payload["output"] = int(audio_output_device) if audio_output_device.isdigit() else audio_output_device
|
|
567
|
+
try:
|
|
568
|
+
requests.post(f"{ROBOVISION_AUDIO_URL.rstrip('/')}/set-device", json=payload, timeout=0.8).raise_for_status()
|
|
569
|
+
except Exception:
|
|
570
|
+
pass
|
|
571
|
+
return jsonify({"video_device": str(current_camera_index), "audio_device": audio_input_device, "audio_output_device": audio_output_device, "source": "robovision_pi"})
|
|
572
572
|
|
|
573
573
|
@app.route('/api/motion/status', methods=['GET'])
|
|
574
574
|
def motion_status():
|
|
@@ -646,11 +646,11 @@ if __name__ == '__main__':
|
|
|
646
646
|
print("RoboVision - Raspberry Pi Vision Server (Minimal)")
|
|
647
647
|
print("=" * 60)
|
|
648
648
|
print(f"Starting Flask server on http://0.0.0.0:{args.port}")
|
|
649
|
-
if webhook_url:
|
|
650
|
-
print(f"Motion webhook: {webhook_url}")
|
|
651
|
-
print(f"Motion detection: {'ARMED' if motion_detection_active else 'off (POST /api/motion/toggle to arm)'}")
|
|
652
|
-
print("=" * 60)
|
|
653
|
-
# Start the single camera owner at boot. Production motion and MJPEG
|
|
654
|
-
# readiness must not depend on an operator opening the dashboard first.
|
|
655
|
-
_ensure_camera_worker()
|
|
656
|
-
app.run(host='0.0.0.0', port=args.port, debug=False, threaded=True)
|
|
649
|
+
if webhook_url:
|
|
650
|
+
print(f"Motion webhook: {webhook_url}")
|
|
651
|
+
print(f"Motion detection: {'ARMED' if motion_detection_active else 'off (POST /api/motion/toggle to arm)'}")
|
|
652
|
+
print("=" * 60)
|
|
653
|
+
# Start the single camera owner at boot. Production motion and MJPEG
|
|
654
|
+
# readiness must not depend on an operator opening the dashboard first.
|
|
655
|
+
_ensure_camera_worker()
|
|
656
|
+
app.run(host='0.0.0.0', port=args.port, debug=False, threaded=True)
|