infinicode 2.8.13 → 2.8.16

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 (38) hide show
  1. package/dist/kernel/federation/dashboard-html.d.ts +1 -1
  2. package/dist/kernel/federation/dashboard-html.js +32 -1
  3. package/dist/robopark/auto-start.d.ts +1 -1
  4. package/dist/robopark/preview-agent-launcher.d.ts +3 -0
  5. package/dist/robopark/preview-agent-launcher.js +8 -0
  6. package/dist/robopark/python-env.d.ts +10 -4
  7. package/dist/robopark/python-env.js +28 -16
  8. package/dist/robopark/setup.d.ts +1 -0
  9. package/dist/robopark/setup.js +22 -0
  10. package/dist/robopark/vision-agent-launcher.d.ts +7 -0
  11. package/dist/robopark/vision-agent-launcher.js +55 -0
  12. package/dist/robopark-cli.js +16 -0
  13. package/package.json +3 -1
  14. package/packages/robopark/pi-client/README.md +17 -0
  15. package/packages/robopark/pi-client/_install_steps.sh +29 -0
  16. package/packages/robopark/pi-client/client.py +305 -0
  17. package/packages/robopark/pi-client/install.sh +41 -0
  18. package/packages/robopark/pi-client/join_convo.sh +55 -0
  19. package/packages/robopark/pi-client/livekit_bridge.py +289 -0
  20. package/packages/robopark/pi-client/motor_bridge.py +82 -0
  21. package/packages/robopark/pi-client/pi_ui.py +375 -0
  22. package/packages/robopark/pi-client/requirements.txt +10 -0
  23. package/packages/robopark/pi-client/robopark-pi-client.service +26 -0
  24. package/packages/robopark/pi-client/robopark-pi-ui.service +24 -0
  25. package/packages/robopark/pi-client/smoke_bridge.py +15 -0
  26. package/packages/robopark/pi-client/stub_motor_server.py +46 -0
  27. package/packages/robopark/scheduler/main.py +45 -9
  28. package/packages/robopark/scheduler/preview_agent.py +132 -3
  29. package/packages/robopark/vision/.env.example +7 -0
  30. package/packages/robopark/vision/README.md +66 -0
  31. package/packages/robopark/vision/app_pi_clean.py +326 -0
  32. package/packages/robopark/vision/audio_server_pi.py +751 -0
  33. package/packages/robopark/vision/install.sh +34 -0
  34. package/packages/robopark/vision/motor_server.py +304 -0
  35. package/packages/robopark/vision/requirements-demo.txt +12 -0
  36. package/packages/robopark/vision/requirements_pi_unified.txt +101 -0
  37. package/packages/robopark/vision/run.sh +244 -0
  38. package/packages/robopark/vision/services/services.sh +12 -0
@@ -0,0 +1,326 @@
1
+ from flask import Flask, Response, jsonify, request
2
+ from flask_cors import CORS
3
+ import argparse
4
+ import cv2
5
+ import os
6
+ import time
7
+ import numpy as np
8
+ import threading
9
+ import requests
10
+ import base64
11
+
12
+ app = Flask(__name__)
13
+ CORS(app)
14
+
15
+ # Camera management
16
+ current_camera_index = 0
17
+ camera = None
18
+
19
+ def get_camera():
20
+ global camera, current_camera_index
21
+ if camera is None or not camera.isOpened():
22
+ camera = cv2.VideoCapture(current_camera_index)
23
+ camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
24
+ camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
25
+ print(f"Camera {current_camera_index} initialized")
26
+ return camera
27
+
28
+ # Global variables
29
+ latest_detections = []
30
+ lock = threading.Lock()
31
+ caption_mode_enabled = False
32
+ motion_detection_active = False
33
+ motion_detected_state = False
34
+ last_motion_time = 0
35
+ motion_frame_buffer = None
36
+ webhook_url = None
37
+ last_webhook_send_time = 0
38
+ webhook_send_interval = 0.5
39
+
40
+ # Simple object detection using OpenCV DNN (MobileNet SSD)
41
+ try:
42
+ # Load pre-trained MobileNet SSD model
43
+ net = cv2.dnn.readNetFromCaffe(
44
+ 'deploy.prototxt',
45
+ 'mobilenet_iter_73000.caffemodel'
46
+ )
47
+ DETECTION_AVAILABLE = True
48
+ print("Object detection model loaded")
49
+ except:
50
+ DETECTION_AVAILABLE = False
51
+ print("Object detection model not found - running without detection")
52
+
53
+ # COCO class labels
54
+ CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat",
55
+ "bottle", "bus", "car", "cat", "chair", "cow", "diningtable",
56
+ "dog", "horse", "motorbike", "person", "pottedplant", "sheep",
57
+ "sofa", "train", "tvmonitor"]
58
+
59
+ def detect_objects(frame, conf_threshold=0.5):
60
+ """Detect objects using OpenCV DNN"""
61
+ if not DETECTION_AVAILABLE:
62
+ return frame, []
63
+
64
+ (h, w) = frame.shape[:2]
65
+ blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 0.007843, (300, 300), 127.5)
66
+ net.setInput(blob)
67
+ detections_dnn = net.forward()
68
+
69
+ detected_objects = []
70
+
71
+ for i in range(detections_dnn.shape[2]):
72
+ confidence = detections_dnn[0, 0, i, 2]
73
+
74
+ if confidence > conf_threshold:
75
+ idx = int(detections_dnn[0, 0, i, 1])
76
+ if idx >= len(CLASSES):
77
+ continue
78
+
79
+ box = detections_dnn[0, 0, i, 3:7] * np.array([w, h, w, h])
80
+ (startX, startY, endX, endY) = box.astype("int")
81
+
82
+ label = CLASSES[idx]
83
+
84
+ # Draw bounding box
85
+ cv2.rectangle(frame, (startX, startY), (endX, endY), (0, 255, 0), 2)
86
+
87
+ # Draw label with confidence
88
+ text = f"{label}: {confidence*100:.1f}%"
89
+ y = startY - 15 if startY - 15 > 15 else startY + 15
90
+ cv2.putText(frame, text, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
91
+
92
+ detected_objects.append({
93
+ "label": label,
94
+ "confidence": float(confidence),
95
+ "bbox": [int(startX), int(startY), int(endX), int(endY)],
96
+ "is_focus": False
97
+ })
98
+
99
+ return frame, detected_objects
100
+
101
+ def send_webhook(frame_data):
102
+ """Send frame to webhook URL"""
103
+ global webhook_url, last_webhook_send_time
104
+
105
+ if not webhook_url:
106
+ return
107
+
108
+ current_time = time.time()
109
+ if current_time - last_webhook_send_time < webhook_send_interval:
110
+ return
111
+
112
+ try:
113
+ _, buffer = cv2.imencode('.jpg', frame_data)
114
+ img_base64 = base64.b64encode(buffer).decode('utf-8')
115
+
116
+ payload = {
117
+ 'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S'),
118
+ 'image': img_base64,
119
+ 'format': 'jpeg',
120
+ 'encoding': 'base64'
121
+ }
122
+
123
+ def send_async():
124
+ try:
125
+ response = requests.post(webhook_url, json=payload, headers={'Content-Type': 'application/json'}, timeout=5)
126
+ if response.status_code == 200:
127
+ print(f"Webhook sent successfully")
128
+ except Exception as e:
129
+ print(f"Webhook error: {e}")
130
+
131
+ thread = threading.Thread(target=send_async, daemon=True)
132
+ thread.start()
133
+ last_webhook_send_time = current_time
134
+ except Exception as e:
135
+ print(f"Error preparing webhook: {e}")
136
+
137
+ def generate_frames():
138
+ global latest_detections, motion_detection_active, motion_detected_state
139
+ global last_motion_time, motion_frame_buffer
140
+
141
+ prev_gray = None
142
+
143
+ while True:
144
+ cam = get_camera()
145
+ if cam is None or not cam.isOpened():
146
+ time.sleep(1)
147
+ continue
148
+
149
+ success, frame = cam.read()
150
+ if not success:
151
+ time.sleep(0.1)
152
+ continue
153
+
154
+ # Run object detection
155
+ processed_frame, detections = detect_objects(frame, conf_threshold=0.5)
156
+
157
+ with lock:
158
+ latest_detections = detections
159
+
160
+ # Motion detection logic
161
+ if motion_detection_active:
162
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
163
+ gray = cv2.GaussianBlur(gray, (21, 21), 0)
164
+
165
+ if prev_gray is not None:
166
+ frame_delta = cv2.absdiff(prev_gray, gray)
167
+ thresh = cv2.threshold(frame_delta, 25, 255, cv2.THRESH_BINARY)[1]
168
+ thresh = cv2.dilate(thresh, None, iterations=2)
169
+ contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
170
+
171
+ motion_detected = False
172
+ for contour in contours:
173
+ if cv2.contourArea(contour) >= 500:
174
+ motion_detected = True
175
+ (x, y, w, h) = cv2.boundingRect(contour)
176
+ cv2.rectangle(processed_frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
177
+ break
178
+
179
+ if motion_detected:
180
+ motion_detected_state = True
181
+ last_motion_time = time.time()
182
+ motion_frame_buffer = processed_frame.copy()
183
+ print(f"Motion detected!")
184
+ send_webhook(processed_frame)
185
+ else:
186
+ motion_detected_state = False
187
+
188
+ prev_gray = gray
189
+
190
+ ret, buffer = cv2.imencode('.jpg', processed_frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
191
+ frame_bytes = buffer.tobytes()
192
+
193
+ yield (b'--frame\r\n'
194
+ b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
195
+
196
+ @app.route('/')
197
+ def index():
198
+ return jsonify({"status": "ok", "message": "RoboVision Pi Server", "version": "1.0"})
199
+
200
+ @app.route('/video_feed')
201
+ def video_feed():
202
+ return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
203
+
204
+ @app.route('/api/detections')
205
+ def get_detections():
206
+ with lock:
207
+ return jsonify(latest_detections)
208
+
209
+ @app.route('/api/caption')
210
+ def get_caption():
211
+ return jsonify({"caption": "Awaiting caption..."})
212
+
213
+ @app.route('/api/caption_mode', methods=['GET', 'POST'])
214
+ def caption_mode():
215
+ global caption_mode_enabled
216
+ if request.method == 'GET':
217
+ return jsonify({"enabled": caption_mode_enabled})
218
+ data = request.json or {}
219
+ caption_mode_enabled = bool(data.get('enabled', False))
220
+ return jsonify({"enabled": caption_mode_enabled})
221
+
222
+ @app.route('/api/cameras', methods=['GET'])
223
+ def list_cameras():
224
+ available = []
225
+ for i in range(4):
226
+ cap = cv2.VideoCapture(i)
227
+ if cap.isOpened():
228
+ available.append({"index": i, "name": f"Camera {i}"})
229
+ cap.release()
230
+ return jsonify({"cameras": available, "current": current_camera_index})
231
+
232
+ @app.route('/api/camera/switch', methods=['POST'])
233
+ def switch_camera():
234
+ global camera, current_camera_index
235
+ data = request.json or {}
236
+ new_index = int(data.get('index', 0))
237
+
238
+ if camera:
239
+ camera.release()
240
+
241
+ current_camera_index = new_index
242
+ camera = None
243
+
244
+ return jsonify({"status": "ok", "camera_index": current_camera_index})
245
+
246
+ @app.route('/api/motion/status', methods=['GET'])
247
+ def motion_status():
248
+ global motion_detection_active, motion_detected_state, last_motion_time
249
+ return jsonify({
250
+ "active": motion_detection_active,
251
+ "motion_detected": motion_detected_state,
252
+ "last_motion": last_motion_time,
253
+ "time_since_motion": time.time() - last_motion_time if last_motion_time > 0 else None
254
+ })
255
+
256
+ @app.route('/api/motion/toggle', methods=['POST'])
257
+ def motion_toggle():
258
+ global motion_detection_active
259
+ data = request.json or {}
260
+ motion_detection_active = bool(data.get('active', False))
261
+ return jsonify({"status": "success", "active": motion_detection_active})
262
+
263
+ @app.route('/api/motion/snapshot', methods=['GET'])
264
+ def motion_snapshot():
265
+ global motion_frame_buffer
266
+ if motion_frame_buffer is not None:
267
+ _, buffer = cv2.imencode('.jpg', motion_frame_buffer, [cv2.IMWRITE_JPEG_QUALITY, 85])
268
+ img_base64 = base64.b64encode(buffer).decode('utf-8')
269
+ return jsonify({
270
+ "image": img_base64,
271
+ "timestamp": time.time()
272
+ })
273
+ return jsonify({"error": "No frame available"}), 404
274
+
275
+ @app.route('/api/motion/webhook', methods=['GET', 'POST'])
276
+ def motion_webhook():
277
+ global webhook_url
278
+
279
+ if request.method == 'GET':
280
+ return jsonify({
281
+ "webhook_url": webhook_url or "",
282
+ "configured": webhook_url is not None and len(webhook_url) > 0
283
+ })
284
+
285
+ data = request.json or {}
286
+ new_url = data.get('url', '').strip()
287
+
288
+ if new_url:
289
+ webhook_url = new_url
290
+ return jsonify({
291
+ "status": "success",
292
+ "message": "Webhook URL configured",
293
+ "webhook_url": webhook_url
294
+ })
295
+ else:
296
+ webhook_url = None
297
+ return jsonify({
298
+ "status": "success",
299
+ "message": "Webhook URL cleared",
300
+ "webhook_url": None
301
+ })
302
+
303
+ if __name__ == '__main__':
304
+ parser = argparse.ArgumentParser(description="RoboVision — camera/motion detection server")
305
+ parser.add_argument("--port", type=int, default=int(os.getenv("VISION_PORT", "5000")))
306
+ parser.add_argument("--motion-webhook-url", default=os.getenv("MOTION_WEBHOOK_URL", ""),
307
+ help="where to POST a snapshot when motion is detected, e.g. http://localhost:5057/")
308
+ parser.add_argument("--motion-active", action="store_true",
309
+ default=os.getenv("MOTION_ACTIVE", "").lower() in ("1", "true", "yes"),
310
+ help="arm motion detection immediately on startup (no manual /api/motion/toggle call needed)")
311
+ args = parser.parse_args()
312
+
313
+ if args.motion_webhook_url:
314
+ webhook_url = args.motion_webhook_url
315
+ if args.motion_active:
316
+ motion_detection_active = True
317
+
318
+ print("=" * 60)
319
+ print("RoboVision - Raspberry Pi Vision Server (Minimal)")
320
+ print("=" * 60)
321
+ print(f"Starting Flask server on http://0.0.0.0:{args.port}")
322
+ if webhook_url:
323
+ print(f"Motion webhook: {webhook_url}")
324
+ print(f"Motion detection: {'ARMED' if motion_detection_active else 'off (POST /api/motion/toggle to arm)'}")
325
+ print("=" * 60)
326
+ app.run(host='0.0.0.0', port=args.port, debug=False, threaded=True)