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.
Files changed (408) hide show
  1. package/README.md +66 -105
  2. package/bin/robopark.js +9 -2
  3. package/conversation/elevenlabs_agent.py +1617 -1617
  4. package/conversation/supervisor_store.py +189 -189
  5. package/dist/{kernel/config-schema.js → config-schema.js} +1 -0
  6. package/dist/config-schema.js.map +1 -0
  7. package/dist/robopark/add-robot.js +1 -0
  8. package/dist/robopark/add-robot.js.map +1 -0
  9. package/dist/robopark/agent-ctl.js +1 -0
  10. package/dist/robopark/agent-ctl.js.map +1 -0
  11. package/dist/robopark/auto-start.js +51 -73
  12. package/dist/robopark/auto-start.js.map +1 -0
  13. package/dist/robopark/conversation.js +1 -0
  14. package/dist/robopark/conversation.js.map +1 -0
  15. package/dist/robopark/discovery.js +1 -0
  16. package/dist/robopark/discovery.js.map +1 -0
  17. package/dist/robopark/doctor.js +1 -0
  18. package/dist/robopark/doctor.js.map +1 -0
  19. package/dist/robopark/enroll.js +1 -0
  20. package/dist/robopark/enroll.js.map +1 -0
  21. package/dist/robopark/llm-set.js +1 -0
  22. package/dist/robopark/llm-set.js.map +1 -0
  23. package/dist/robopark/motor-control.js +1 -0
  24. package/dist/robopark/motor-control.js.map +1 -0
  25. package/dist/robopark/preview-agent-launcher.js +1 -0
  26. package/dist/robopark/preview-agent-launcher.js.map +1 -0
  27. package/dist/robopark/probe.js +16 -15
  28. package/dist/robopark/probe.js.map +1 -0
  29. package/dist/robopark/profile.js +1 -0
  30. package/dist/robopark/profile.js.map +1 -0
  31. package/dist/robopark/python-env.js +1 -7
  32. package/dist/robopark/python-env.js.map +1 -0
  33. package/dist/robopark/robot-runtime.js +1 -0
  34. package/dist/robopark/robot-runtime.js.map +1 -0
  35. package/dist/robopark/scan.js +1 -0
  36. package/dist/robopark/scan.js.map +1 -0
  37. package/dist/robopark/screen-control.js +1 -0
  38. package/dist/robopark/screen-control.js.map +1 -0
  39. package/dist/robopark/secrets.js +1 -0
  40. package/dist/robopark/secrets.js.map +1 -0
  41. package/dist/robopark/serve.js +6 -8
  42. package/dist/robopark/serve.js.map +1 -0
  43. package/dist/robopark/server-add.js +1 -0
  44. package/dist/robopark/server-add.js.map +1 -0
  45. package/dist/robopark/setup-livekit.js +53 -52
  46. package/dist/robopark/setup-livekit.js.map +1 -0
  47. package/dist/robopark/setup.js +1 -0
  48. package/dist/robopark/setup.js.map +1 -0
  49. package/dist/robopark/stop-all.js +1 -0
  50. package/dist/robopark/stop-all.js.map +1 -0
  51. package/dist/robopark/verify.js +1 -0
  52. package/dist/robopark/verify.js.map +1 -0
  53. package/dist/robopark/vision-agent-launcher.js +1 -0
  54. package/dist/robopark/vision-agent-launcher.js.map +1 -0
  55. package/dist/robopark/vision-control.js +1 -0
  56. package/dist/robopark/vision-control.js.map +1 -0
  57. package/dist/robopark-cli.js +7 -125
  58. package/dist/robopark-cli.js.map +1 -0
  59. package/package.json +16 -56
  60. package/pi-client/_install_steps.sh +29 -29
  61. package/pi-client/client.py +76 -76
  62. package/pi-client/install.sh +40 -40
  63. package/pi-client/join_convo.sh +54 -54
  64. package/pi-client/livekit_bridge.py +24 -24
  65. package/pi-client/motor_bridge.py +10 -10
  66. package/scheduler/__pycache__/main.cpython-312.pyc +0 -0
  67. package/scheduler/main.py +196 -17
  68. package/scheduler/media_lock.py +57 -57
  69. package/scheduler/preview_agent.py +1122 -1122
  70. package/scheduler/production_config.json +73 -113
  71. package/scheduler/scripts/install-robot-supervisor-linux.sh +33 -33
  72. package/{ui/standalone/public → scheduler/static}/voice-join.html +101 -13
  73. package/screen/screen_runtime.py +75 -75
  74. package/vision/app_pi_clean.py +297 -297
  75. package/vision/audio_server_pi.py +24 -24
  76. package/vision/install.sh +34 -34
  77. package/vision/motor_server.py +275 -275
  78. package/vision/requirements_camera.txt +6 -6
  79. package/vision/requirements_motor.txt +4 -4
  80. package/vision/requirements_pi_unified.txt +2 -2
  81. package/vision/requirements_vision_agent.txt +19 -19
  82. package/vision/run.sh +244 -244
  83. package/vision/services/services.sh +12 -12
  84. package/dist/kernel/types.js +0 -7
  85. package/dist/robopark/access.js +0 -99
  86. package/dist/robopark/control-center-html.js +0 -9191
  87. package/dist/robopark/control-server.js +0 -148
  88. package/dist/robopark/deployment-commands.js +0 -47
  89. package/dist/robopark/frontend-service.js +0 -80
  90. package/dist/robopark/standalone.js +0 -584
  91. package/dist/robopark/voice-join-html.js +0 -57
  92. package/scripts/windows-shim-fix.cjs +0 -58
  93. package/static/robopark/barracuda.png +0 -0
  94. package/static/robopark/bmw.png +0 -0
  95. package/static/robopark/jaguar.png +0 -0
  96. package/static/robopark/magnus.jpg +0 -0
  97. package/static/robopark/panda.png +0 -0
  98. package/static/robopark/tesla.png +0 -0
  99. package/static/robopark/titan.jpg +0 -0
  100. package/static/robopark/volt.png +0 -0
  101. package/static/vendor/elevenlabs-client.LICENSE +0 -21
  102. package/static/vendor/elevenlabs-client.iife.js +0 -24200
  103. package/ui/ROBOVOICE_BUILD.json +0 -9
  104. package/ui/standalone/.next/BUILD_ID +0 -1
  105. package/ui/standalone/.next/app-build-manifest.json +0 -391
  106. package/ui/standalone/.next/app-path-routes-manifest.json +0 -55
  107. package/ui/standalone/.next/build-manifest.json +0 -33
  108. package/ui/standalone/.next/package.json +0 -1
  109. package/ui/standalone/.next/prerender-manifest.json +0 -41
  110. package/ui/standalone/.next/react-loadable-manifest.json +0 -14
  111. package/ui/standalone/.next/required-server-files.json +0 -362
  112. package/ui/standalone/.next/routes-manifest.json +0 -246
  113. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route.js +0 -1
  114. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route.js.nft.json +0 -1
  115. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route_client-reference-manifest.js +0 -1
  116. package/ui/standalone/.next/server/app/(app)/page.js +0 -2
  117. package/ui/standalone/.next/server/app/(app)/page.js.nft.json +0 -1
  118. package/ui/standalone/.next/server/app/(app)/page_client-reference-manifest.js +0 -1
  119. package/ui/standalone/.next/server/app/(app)/voice/page.js +0 -12
  120. package/ui/standalone/.next/server/app/(app)/voice/page.js.nft.json +0 -1
  121. package/ui/standalone/.next/server/app/(app)/voice/page_client-reference-manifest.js +0 -1
  122. package/ui/standalone/.next/server/app/_not-found/page.js +0 -12
  123. package/ui/standalone/.next/server/app/_not-found/page.js.nft.json +0 -1
  124. package/ui/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  125. package/ui/standalone/.next/server/app/api/connection-details/route.js +0 -1
  126. package/ui/standalone/.next/server/app/api/connection-details/route.js.nft.json +0 -1
  127. package/ui/standalone/.next/server/app/api/connection-details/route_client-reference-manifest.js +0 -1
  128. package/ui/standalone/.next/server/app/api/device-character/route.js +0 -1
  129. package/ui/standalone/.next/server/app/api/device-character/route.js.nft.json +0 -1
  130. package/ui/standalone/.next/server/app/api/device-character/route_client-reference-manifest.js +0 -1
  131. package/ui/standalone/.next/server/app/api/download-piper-model/route.js +0 -1
  132. package/ui/standalone/.next/server/app/api/download-piper-model/route.js.nft.json +0 -1
  133. package/ui/standalone/.next/server/app/api/download-piper-model/route_client-reference-manifest.js +0 -1
  134. package/ui/standalone/.next/server/app/api/model-status/route.js +0 -1
  135. package/ui/standalone/.next/server/app/api/model-status/route.js.nft.json +0 -1
  136. package/ui/standalone/.next/server/app/api/model-status/route_client-reference-manifest.js +0 -1
  137. package/ui/standalone/.next/server/app/api/models/route.js +0 -1
  138. package/ui/standalone/.next/server/app/api/models/route.js.nft.json +0 -1
  139. package/ui/standalone/.next/server/app/api/models/route_client-reference-manifest.js +0 -1
  140. package/ui/standalone/.next/server/app/api/prewarm/route.js +0 -1
  141. package/ui/standalone/.next/server/app/api/prewarm/route.js.nft.json +0 -1
  142. package/ui/standalone/.next/server/app/api/prewarm/route_client-reference-manifest.js +0 -1
  143. package/ui/standalone/.next/server/app/api/prompt/route.js +0 -1
  144. package/ui/standalone/.next/server/app/api/prompt/route.js.nft.json +0 -1
  145. package/ui/standalone/.next/server/app/api/prompt/route_client-reference-manifest.js +0 -1
  146. package/ui/standalone/.next/server/app/api/reload-tools/route.js +0 -1
  147. package/ui/standalone/.next/server/app/api/reload-tools/route.js.nft.json +0 -1
  148. package/ui/standalone/.next/server/app/api/reload-tools/route_client-reference-manifest.js +0 -1
  149. package/ui/standalone/.next/server/app/api/robopark/camera-token/route.js +0 -1
  150. package/ui/standalone/.next/server/app/api/robopark/camera-token/route.js.nft.json +0 -1
  151. package/ui/standalone/.next/server/app/api/robopark/camera-token/route_client-reference-manifest.js +0 -1
  152. package/ui/standalone/.next/server/app/api/robopark/character-ai/route.js +0 -1
  153. package/ui/standalone/.next/server/app/api/robopark/character-ai/route.js.nft.json +0 -1
  154. package/ui/standalone/.next/server/app/api/robopark/character-ai/route_client-reference-manifest.js +0 -1
  155. package/ui/standalone/.next/server/app/api/robopark/device-access/route.js +0 -1
  156. package/ui/standalone/.next/server/app/api/robopark/device-access/route.js.nft.json +0 -1
  157. package/ui/standalone/.next/server/app/api/robopark/device-access/route_client-reference-manifest.js +0 -1
  158. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route.js +0 -1
  159. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route.js.nft.json +0 -1
  160. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route_client-reference-manifest.js +0 -1
  161. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route.js +0 -1
  162. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route.js.nft.json +0 -1
  163. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route_client-reference-manifest.js +0 -1
  164. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route.js +0 -1
  165. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route.js.nft.json +0 -1
  166. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route_client-reference-manifest.js +0 -1
  167. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route.js +0 -1
  168. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route.js.nft.json +0 -1
  169. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route_client-reference-manifest.js +0 -1
  170. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route.js +0 -1
  171. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route.js.nft.json +0 -1
  172. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route_client-reference-manifest.js +0 -1
  173. package/ui/standalone/.next/server/app/api/robopark/robots/route.js +0 -1
  174. package/ui/standalone/.next/server/app/api/robopark/robots/route.js.nft.json +0 -1
  175. package/ui/standalone/.next/server/app/api/robopark/robots/route_client-reference-manifest.js +0 -1
  176. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route.js +0 -1
  177. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route.js.nft.json +0 -1
  178. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route_client-reference-manifest.js +0 -1
  179. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route.js +0 -1
  180. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route.js.nft.json +0 -1
  181. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route_client-reference-manifest.js +0 -1
  182. package/ui/standalone/.next/server/app/api/robopark/servers/route.js +0 -1
  183. package/ui/standalone/.next/server/app/api/robopark/servers/route.js.nft.json +0 -1
  184. package/ui/standalone/.next/server/app/api/robopark/servers/route_client-reference-manifest.js +0 -1
  185. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route.js +0 -1
  186. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route.js.nft.json +0 -1
  187. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route_client-reference-manifest.js +0 -1
  188. package/ui/standalone/.next/server/app/api/robopark/sessions/route.js +0 -1
  189. package/ui/standalone/.next/server/app/api/robopark/sessions/route.js.nft.json +0 -1
  190. package/ui/standalone/.next/server/app/api/robopark/sessions/route_client-reference-manifest.js +0 -1
  191. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route.js +0 -1
  192. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route.js.nft.json +0 -1
  193. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route_client-reference-manifest.js +0 -1
  194. package/ui/standalone/.next/server/app/api/robopark/transcripts/route.js +0 -1
  195. package/ui/standalone/.next/server/app/api/robopark/transcripts/route.js.nft.json +0 -1
  196. package/ui/standalone/.next/server/app/api/robopark/transcripts/route_client-reference-manifest.js +0 -1
  197. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route.js +0 -1
  198. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route.js.nft.json +0 -1
  199. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route_client-reference-manifest.js +0 -1
  200. package/ui/standalone/.next/server/app/api/settings/route.js +0 -1
  201. package/ui/standalone/.next/server/app/api/settings/route.js.nft.json +0 -1
  202. package/ui/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +0 -1
  203. package/ui/standalone/.next/server/app/api/setup/complete/route.js +0 -1
  204. package/ui/standalone/.next/server/app/api/setup/complete/route.js.nft.json +0 -1
  205. package/ui/standalone/.next/server/app/api/setup/complete/route_client-reference-manifest.js +0 -1
  206. package/ui/standalone/.next/server/app/api/setup/status/route.js +0 -1
  207. package/ui/standalone/.next/server/app/api/setup/status/route.js.nft.json +0 -1
  208. package/ui/standalone/.next/server/app/api/setup/status/route_client-reference-manifest.js +0 -1
  209. package/ui/standalone/.next/server/app/api/setup/test-groq/route.js +0 -1
  210. package/ui/standalone/.next/server/app/api/setup/test-groq/route.js.nft.json +0 -1
  211. package/ui/standalone/.next/server/app/api/setup/test-groq/route_client-reference-manifest.js +0 -1
  212. package/ui/standalone/.next/server/app/api/setup/test-hass/route.js +0 -1
  213. package/ui/standalone/.next/server/app/api/setup/test-hass/route.js.nft.json +0 -1
  214. package/ui/standalone/.next/server/app/api/setup/test-hass/route_client-reference-manifest.js +0 -1
  215. package/ui/standalone/.next/server/app/api/setup/test-n8n/route.js +0 -1
  216. package/ui/standalone/.next/server/app/api/setup/test-n8n/route.js.nft.json +0 -1
  217. package/ui/standalone/.next/server/app/api/setup/test-n8n/route_client-reference-manifest.js +0 -1
  218. package/ui/standalone/.next/server/app/api/setup/test-ollama/route.js +0 -1
  219. package/ui/standalone/.next/server/app/api/setup/test-ollama/route.js.nft.json +0 -1
  220. package/ui/standalone/.next/server/app/api/setup/test-ollama/route_client-reference-manifest.js +0 -1
  221. package/ui/standalone/.next/server/app/api/test-tts/route.js +0 -1
  222. package/ui/standalone/.next/server/app/api/test-tts/route.js.nft.json +0 -1
  223. package/ui/standalone/.next/server/app/api/test-tts/route_client-reference-manifest.js +0 -1
  224. package/ui/standalone/.next/server/app/api/turn-credentials/route.js +0 -1
  225. package/ui/standalone/.next/server/app/api/turn-credentials/route.js.nft.json +0 -1
  226. package/ui/standalone/.next/server/app/api/turn-credentials/route_client-reference-manifest.js +0 -1
  227. package/ui/standalone/.next/server/app/api/voices/route.js +0 -1
  228. package/ui/standalone/.next/server/app/api/voices/route.js.nft.json +0 -1
  229. package/ui/standalone/.next/server/app/api/voices/route_client-reference-manifest.js +0 -1
  230. package/ui/standalone/.next/server/app/api/wake/route.js +0 -1
  231. package/ui/standalone/.next/server/app/api/wake/route.js.nft.json +0 -1
  232. package/ui/standalone/.next/server/app/api/wake/route_client-reference-manifest.js +0 -1
  233. package/ui/standalone/.next/server/app/api/wake-word/models/route.js +0 -1
  234. package/ui/standalone/.next/server/app/api/wake-word/models/route.js.nft.json +0 -1
  235. package/ui/standalone/.next/server/app/api/wake-word/models/route_client-reference-manifest.js +0 -1
  236. package/ui/standalone/.next/server/app/api/wake-word/status/route.js +0 -1
  237. package/ui/standalone/.next/server/app/api/wake-word/status/route.js.nft.json +0 -1
  238. package/ui/standalone/.next/server/app/api/wake-word/status/route_client-reference-manifest.js +0 -1
  239. package/ui/standalone/.next/server/app/api/wake-word/upload/route.js +0 -1
  240. package/ui/standalone/.next/server/app/api/wake-word/upload/route.js.nft.json +0 -1
  241. package/ui/standalone/.next/server/app/api/wake-word/upload/route_client-reference-manifest.js +0 -1
  242. package/ui/standalone/.next/server/app/favicon.ico/route.js +0 -1
  243. package/ui/standalone/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  244. package/ui/standalone/.next/server/app/favicon.ico.body +0 -0
  245. package/ui/standalone/.next/server/app/favicon.ico.meta +0 -1
  246. package/ui/standalone/.next/server/app/fed/[...path]/route.js +0 -4
  247. package/ui/standalone/.next/server/app/fed/[...path]/route.js.nft.json +0 -1
  248. package/ui/standalone/.next/server/app/fed/[...path]/route_client-reference-manifest.js +0 -1
  249. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route.js +0 -1
  250. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route.js.nft.json +0 -1
  251. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route_client-reference-manifest.js +0 -1
  252. package/ui/standalone/.next/server/app/robopark/[...path]/route.js +0 -1
  253. package/ui/standalone/.next/server/app/robopark/[...path]/route.js.nft.json +0 -1
  254. package/ui/standalone/.next/server/app/robopark/[...path]/route_client-reference-manifest.js +0 -1
  255. package/ui/standalone/.next/server/app/robopark/api/[...path]/route.js +0 -1
  256. package/ui/standalone/.next/server/app/robopark/api/[...path]/route.js.nft.json +0 -1
  257. package/ui/standalone/.next/server/app/robopark/api/[...path]/route_client-reference-manifest.js +0 -1
  258. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route.js +0 -1
  259. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route.js.nft.json +0 -1
  260. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route_client-reference-manifest.js +0 -1
  261. package/ui/standalone/.next/server/app/robopark/page.js +0 -2
  262. package/ui/standalone/.next/server/app/robopark/page.js.nft.json +0 -1
  263. package/ui/standalone/.next/server/app/robopark/page_client-reference-manifest.js +0 -1
  264. package/ui/standalone/.next/server/app/tailnet/route.js +0 -1
  265. package/ui/standalone/.next/server/app/tailnet/route.js.nft.json +0 -1
  266. package/ui/standalone/.next/server/app/tailnet/route_client-reference-manifest.js +0 -1
  267. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route.js +0 -1
  268. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route.js.nft.json +0 -1
  269. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route_client-reference-manifest.js +0 -1
  270. package/ui/standalone/.next/server/app/voice-tester/page.js +0 -2
  271. package/ui/standalone/.next/server/app/voice-tester/page.js.nft.json +0 -1
  272. package/ui/standalone/.next/server/app/voice-tester/page_client-reference-manifest.js +0 -1
  273. package/ui/standalone/.next/server/app-paths-manifest.json +0 -55
  274. package/ui/standalone/.next/server/chunks/1428.js +0 -1
  275. package/ui/standalone/.next/server/chunks/1692.js +0 -1
  276. package/ui/standalone/.next/server/chunks/37.js +0 -1
  277. package/ui/standalone/.next/server/chunks/4876.js +0 -1
  278. package/ui/standalone/.next/server/chunks/5232.js +0 -9
  279. package/ui/standalone/.next/server/chunks/5455.js +0 -11
  280. package/ui/standalone/.next/server/chunks/5611.js +0 -6
  281. package/ui/standalone/.next/server/chunks/5873.js +0 -22
  282. package/ui/standalone/.next/server/chunks/6018.js +0 -1
  283. package/ui/standalone/.next/server/chunks/8755.js +0 -1
  284. package/ui/standalone/.next/server/chunks/9077.js +0 -11
  285. package/ui/standalone/.next/server/functions-config-manifest.json +0 -9
  286. package/ui/standalone/.next/server/middleware-build-manifest.js +0 -1
  287. package/ui/standalone/.next/server/middleware-manifest.json +0 -6
  288. package/ui/standalone/.next/server/middleware-react-loadable-manifest.js +0 -1
  289. package/ui/standalone/.next/server/next-font-manifest.js +0 -1
  290. package/ui/standalone/.next/server/next-font-manifest.json +0 -1
  291. package/ui/standalone/.next/server/pages/500.html +0 -1
  292. package/ui/standalone/.next/server/pages/_app.js +0 -1
  293. package/ui/standalone/.next/server/pages/_app.js.nft.json +0 -1
  294. package/ui/standalone/.next/server/pages/_document.js +0 -1
  295. package/ui/standalone/.next/server/pages/_document.js.nft.json +0 -1
  296. package/ui/standalone/.next/server/pages/_error.js +0 -19
  297. package/ui/standalone/.next/server/pages/_error.js.nft.json +0 -1
  298. package/ui/standalone/.next/server/pages-manifest.json +0 -5
  299. package/ui/standalone/.next/server/server-reference-manifest.js +0 -1
  300. package/ui/standalone/.next/server/server-reference-manifest.json +0 -1
  301. package/ui/standalone/.next/server/webpack-runtime.js +0 -1
  302. package/ui/standalone/.next/static/T3eGaQUuZNb6rhEStcouc/_buildManifest.js +0 -1
  303. package/ui/standalone/.next/static/T3eGaQUuZNb6rhEStcouc/_ssgManifest.js +0 -1
  304. package/ui/standalone/.next/static/chunks/1255-404bb8de49c90395.js +0 -1
  305. package/ui/standalone/.next/static/chunks/144d3bae-bd5d150c0cab972e.js +0 -1
  306. package/ui/standalone/.next/static/chunks/1646.9123ee47220ed70b.js +0 -1
  307. package/ui/standalone/.next/static/chunks/2333-43c39acb531f7d4e.js +0 -3
  308. package/ui/standalone/.next/static/chunks/4bd1b696-f785427dddbba9fb.js +0 -1
  309. package/ui/standalone/.next/static/chunks/5139.c5e46d26064a85db.js +0 -1
  310. package/ui/standalone/.next/static/chunks/76e8f37e-2ae0e3fe7b44d874.js +0 -1
  311. package/ui/standalone/.next/static/chunks/9880-53262785284464cc.js +0 -1
  312. package/ui/standalone/.next/static/chunks/adfb71a7-625c1b5ebd039b6a.js +0 -1
  313. package/ui/standalone/.next/static/chunks/app/(app)/layout-5f312b128a0caf75.js +0 -1
  314. package/ui/standalone/.next/static/chunks/app/(app)/opengraph-image-xg4ifa/route-5f312b128a0caf75.js +0 -1
  315. package/ui/standalone/.next/static/chunks/app/(app)/page-5f312b128a0caf75.js +0 -1
  316. package/ui/standalone/.next/static/chunks/app/(app)/voice/page-72d72d9a70d9c1d4.js +0 -1
  317. package/ui/standalone/.next/static/chunks/app/_not-found/page-cfd7daa82d71540c.js +0 -1
  318. package/ui/standalone/.next/static/chunks/app/api/connection-details/route-5f312b128a0caf75.js +0 -1
  319. package/ui/standalone/.next/static/chunks/app/api/device-character/route-5f312b128a0caf75.js +0 -1
  320. package/ui/standalone/.next/static/chunks/app/api/download-piper-model/route-5f312b128a0caf75.js +0 -1
  321. package/ui/standalone/.next/static/chunks/app/api/model-status/route-5f312b128a0caf75.js +0 -1
  322. package/ui/standalone/.next/static/chunks/app/api/models/route-5f312b128a0caf75.js +0 -1
  323. package/ui/standalone/.next/static/chunks/app/api/prewarm/route-5f312b128a0caf75.js +0 -1
  324. package/ui/standalone/.next/static/chunks/app/api/prompt/route-5f312b128a0caf75.js +0 -1
  325. package/ui/standalone/.next/static/chunks/app/api/reload-tools/route-5f312b128a0caf75.js +0 -1
  326. package/ui/standalone/.next/static/chunks/app/api/robopark/camera-token/route-5f312b128a0caf75.js +0 -1
  327. package/ui/standalone/.next/static/chunks/app/api/robopark/character-ai/route-5f312b128a0caf75.js +0 -1
  328. package/ui/standalone/.next/static/chunks/app/api/robopark/device-access/route-5f312b128a0caf75.js +0 -1
  329. package/ui/standalone/.next/static/chunks/app/api/robopark/devices/[deviceId]/control/route-5f312b128a0caf75.js +0 -1
  330. package/ui/standalone/.next/static/chunks/app/api/robopark/live-rooms/route-5f312b128a0caf75.js +0 -1
  331. package/ui/standalone/.next/static/chunks/app/api/robopark/monitor-token/route-5f312b128a0caf75.js +0 -1
  332. package/ui/standalone/.next/static/chunks/app/api/robopark/robots/[robotId]/operations/route-5f312b128a0caf75.js +0 -1
  333. package/ui/standalone/.next/static/chunks/app/api/robopark/robots/[robotId]/voice-engine/route-5f312b128a0caf75.js +0 -1
  334. package/ui/standalone/.next/static/chunks/app/api/robopark/robots/route-5f312b128a0caf75.js +0 -1
  335. package/ui/standalone/.next/static/chunks/app/api/robopark/servers/[serverId]/metrics/route-5f312b128a0caf75.js +0 -1
  336. package/ui/standalone/.next/static/chunks/app/api/robopark/servers/[serverId]/route-5f312b128a0caf75.js +0 -1
  337. package/ui/standalone/.next/static/chunks/app/api/robopark/servers/route-5f312b128a0caf75.js +0 -1
  338. package/ui/standalone/.next/static/chunks/app/api/robopark/sessions/[sessionId]/end/route-5f312b128a0caf75.js +0 -1
  339. package/ui/standalone/.next/static/chunks/app/api/robopark/sessions/route-5f312b128a0caf75.js +0 -1
  340. package/ui/standalone/.next/static/chunks/app/api/robopark/transcripts/[sessionId]/route-5f312b128a0caf75.js +0 -1
  341. package/ui/standalone/.next/static/chunks/app/api/robopark/transcripts/route-5f312b128a0caf75.js +0 -1
  342. package/ui/standalone/.next/static/chunks/app/api/robovoice/robots/[robotId]/media-health/route-5f312b128a0caf75.js +0 -1
  343. package/ui/standalone/.next/static/chunks/app/api/settings/route-5f312b128a0caf75.js +0 -1
  344. package/ui/standalone/.next/static/chunks/app/api/setup/complete/route-5f312b128a0caf75.js +0 -1
  345. package/ui/standalone/.next/static/chunks/app/api/setup/status/route-5f312b128a0caf75.js +0 -1
  346. package/ui/standalone/.next/static/chunks/app/api/setup/test-groq/route-5f312b128a0caf75.js +0 -1
  347. package/ui/standalone/.next/static/chunks/app/api/setup/test-hass/route-5f312b128a0caf75.js +0 -1
  348. package/ui/standalone/.next/static/chunks/app/api/setup/test-n8n/route-5f312b128a0caf75.js +0 -1
  349. package/ui/standalone/.next/static/chunks/app/api/setup/test-ollama/route-5f312b128a0caf75.js +0 -1
  350. package/ui/standalone/.next/static/chunks/app/api/test-tts/route-5f312b128a0caf75.js +0 -1
  351. package/ui/standalone/.next/static/chunks/app/api/turn-credentials/route-5f312b128a0caf75.js +0 -1
  352. package/ui/standalone/.next/static/chunks/app/api/voices/route-5f312b128a0caf75.js +0 -1
  353. package/ui/standalone/.next/static/chunks/app/api/wake/route-5f312b128a0caf75.js +0 -1
  354. package/ui/standalone/.next/static/chunks/app/api/wake-word/models/route-5f312b128a0caf75.js +0 -1
  355. package/ui/standalone/.next/static/chunks/app/api/wake-word/status/route-5f312b128a0caf75.js +0 -1
  356. package/ui/standalone/.next/static/chunks/app/api/wake-word/upload/route-5f312b128a0caf75.js +0 -1
  357. package/ui/standalone/.next/static/chunks/app/fed/[...path]/route-5f312b128a0caf75.js +0 -1
  358. package/ui/standalone/.next/static/chunks/app/fed/media/robots/[robotId]/video_feed/route-5f312b128a0caf75.js +0 -1
  359. package/ui/standalone/.next/static/chunks/app/layout-fcce963da1d8220f.js +0 -1
  360. package/ui/standalone/.next/static/chunks/app/robopark/[...path]/route-5f312b128a0caf75.js +0 -1
  361. package/ui/standalone/.next/static/chunks/app/robopark/api/[...path]/route-5f312b128a0caf75.js +0 -1
  362. package/ui/standalone/.next/static/chunks/app/robopark/api/robots/[robotId]/stream/route-5f312b128a0caf75.js +0 -1
  363. package/ui/standalone/.next/static/chunks/app/robopark/page-5f312b128a0caf75.js +0 -1
  364. package/ui/standalone/.next/static/chunks/app/tailnet/route-5f312b128a0caf75.js +0 -1
  365. package/ui/standalone/.next/static/chunks/app/vendor/livekit-client.umd.js/route-5f312b128a0caf75.js +0 -1
  366. package/ui/standalone/.next/static/chunks/app/voice-tester/page-ae699e048e0eb834.js +0 -1
  367. package/ui/standalone/.next/static/chunks/framework-e60c938074ff7136.js +0 -1
  368. package/ui/standalone/.next/static/chunks/main-9e24e8708ff075fc.js +0 -1
  369. package/ui/standalone/.next/static/chunks/main-app-a1f59168303c7f84.js +0 -1
  370. package/ui/standalone/.next/static/chunks/pages/_app-131c90850aef965b.js +0 -1
  371. package/ui/standalone/.next/static/chunks/pages/_error-e4ba546eb376bdf4.js +0 -1
  372. package/ui/standalone/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  373. package/ui/standalone/.next/static/chunks/webpack-5252e5254da079b5.js +0 -1
  374. package/ui/standalone/.next/static/css/3f478a573f9be82e.css +0 -1
  375. package/ui/standalone/.next/static/css/51560c4af1d5a21f.css +0 -1
  376. package/ui/standalone/.next/static/media/27fe93087559d480-s.p.otf +0 -0
  377. package/ui/standalone/.next/static/media/4d3869dd8a56345a-s.p.otf +0 -0
  378. package/ui/standalone/.next/static/media/63f3bb9ce29e22d5-s.p.otf +0 -0
  379. package/ui/standalone/.next/static/media/79e1c5d0f2fe9f68-s.p.otf +0 -0
  380. package/ui/standalone/.next/static/media/8bf7b2ceda89477b-s.p.woff2 +0 -0
  381. package/ui/standalone/.next/static/media/98baa4a7809fbf72-s.woff2 +0 -0
  382. package/ui/standalone/.next/static/media/f7650dfaa438c37c-s.woff2 +0 -0
  383. package/ui/standalone/package.json +0 -57
  384. package/ui/standalone/public/caal-logo.svg +0 -3
  385. package/ui/standalone/public/commit-mono-400-regular.woff +0 -0
  386. package/ui/standalone/public/cwl-logo-round.png +0 -0
  387. package/ui/standalone/public/everett-light.woff +0 -0
  388. package/ui/standalone/public/hey_cal.ppn +0 -0
  389. package/ui/standalone/public/install-robot-kiosk.sh +0 -242
  390. package/ui/standalone/public/lk-logo-dark.svg +0 -18
  391. package/ui/standalone/public/lk-logo.svg +0 -18
  392. package/ui/standalone/public/lk-wordmark.svg +0 -12
  393. package/ui/standalone/public/opengraph-image-bg.png +0 -0
  394. package/ui/standalone/public/porcupine_params.pv +0 -0
  395. package/ui/standalone/public/robopark-control.html +0 -9200
  396. package/ui/standalone/public/static/robopark/barracuda.png +0 -0
  397. package/ui/standalone/public/static/robopark/bmw.png +0 -0
  398. package/ui/standalone/public/static/robopark/jaguar.png +0 -0
  399. package/ui/standalone/public/static/robopark/magnus.jpg +0 -0
  400. package/ui/standalone/public/static/robopark/panda.png +0 -0
  401. package/ui/standalone/public/static/robopark/tesla.png +0 -0
  402. package/ui/standalone/public/static/robopark/titan.jpg +0 -0
  403. package/ui/standalone/public/static/robopark/volt.png +0 -0
  404. package/ui/standalone/public/tailnet/index.html +0 -8995
  405. package/ui/standalone/public/transcript-analytics.html +0 -72
  406. package/ui/standalone/public/vendor/livekit-client.umd.js +0 -2
  407. package/ui/standalone/server.js +0 -38
  408. /package/{ui/standalone/public → scheduler/static}/vendor/elevenlabs-client.iife.js +0 -0
@@ -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)