infinicode 2.8.13 → 2.8.15

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 (30) 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/preview-agent-launcher.d.ts +3 -0
  4. package/dist/robopark/preview-agent-launcher.js +8 -0
  5. package/dist/robopark-cli.js +3 -0
  6. package/package.json +3 -1
  7. package/packages/robopark/pi-client/README.md +17 -0
  8. package/packages/robopark/pi-client/_install_steps.sh +29 -0
  9. package/packages/robopark/pi-client/client.py +305 -0
  10. package/packages/robopark/pi-client/install.sh +41 -0
  11. package/packages/robopark/pi-client/join_convo.sh +55 -0
  12. package/packages/robopark/pi-client/livekit_bridge.py +289 -0
  13. package/packages/robopark/pi-client/motor_bridge.py +82 -0
  14. package/packages/robopark/pi-client/pi_ui.py +375 -0
  15. package/packages/robopark/pi-client/requirements.txt +10 -0
  16. package/packages/robopark/pi-client/robopark-pi-client.service +26 -0
  17. package/packages/robopark/pi-client/robopark-pi-ui.service +24 -0
  18. package/packages/robopark/pi-client/smoke_bridge.py +15 -0
  19. package/packages/robopark/pi-client/stub_motor_server.py +46 -0
  20. package/packages/robopark/scheduler/main.py +45 -9
  21. package/packages/robopark/scheduler/preview_agent.py +132 -3
  22. package/packages/robopark/vision/.env.example +7 -0
  23. package/packages/robopark/vision/README.md +63 -0
  24. package/packages/robopark/vision/app_pi_clean.py +307 -0
  25. package/packages/robopark/vision/audio_server_pi.py +751 -0
  26. package/packages/robopark/vision/install.sh +34 -0
  27. package/packages/robopark/vision/motor_server.py +304 -0
  28. package/packages/robopark/vision/requirements_pi_unified.txt +101 -0
  29. package/packages/robopark/vision/run.sh +244 -0
  30. package/packages/robopark/vision/services/services.sh +12 -0
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ VENV_DIR="$ROOT_DIR/venv"
6
+ REQ_FILE="$ROOT_DIR/requirements_pi_unified.txt"
7
+
8
+ if ! command -v python3 >/dev/null 2>&1; then
9
+ echo "python3 not found"
10
+ exit 1
11
+ fi
12
+
13
+ if [ ! -f "$REQ_FILE" ]; then
14
+ echo "Missing $REQ_FILE"
15
+ exit 1
16
+ fi
17
+
18
+ echo "System packages (run once):"
19
+ echo "sudo apt-get update"
20
+ echo "sudo apt-get install -y python3-opencv python3-numpy python3-pil libatlas-base-dev libopenblas-dev libhdf5-dev libhdf5-serial-dev libhdf5-103 libqt5gui5 libqt5widgets5 libqt5test5 portaudio19-dev libsndfile1 alsa-utils bluetooth bluez bluez-tools pulseaudio pulseaudio-module-bluetooth python3-pyaudio"
21
+ echo ""
22
+
23
+ echo "Creating venv: $VENV_DIR"
24
+ if [ ! -d "$VENV_DIR" ]; then
25
+ python3 -m venv "$VENV_DIR" --system-site-packages
26
+ fi
27
+
28
+ source "$VENV_DIR/bin/activate"
29
+ python -m pip install --upgrade pip
30
+ python -m pip install -r "$REQ_FILE"
31
+
32
+ echo ""
33
+ echo "Done"
34
+ echo "Next: ./run.sh start-term"
@@ -0,0 +1,304 @@
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel
4
+ from typing import Optional, List
5
+ import time
6
+ import threading
7
+ import json
8
+ import os
9
+
10
+ app = FastAPI(title="Motor Control API", version="1.0.0")
11
+
12
+ # CORS Configuration - Allow all origins for cross-origin requests
13
+ app.add_middleware(
14
+ CORSMiddleware,
15
+ allow_origins=["*"], # Allows all origins
16
+ allow_credentials=True,
17
+ allow_methods=["*"], # Allows all methods (GET, POST, PUT, DELETE, OPTIONS)
18
+ allow_headers=["*"], # Allows all headers
19
+ )
20
+
21
+ # Storage file
22
+ MOTORS_FILE = "motors.json"
23
+
24
+ # In-memory storage
25
+ motors = {}
26
+ active_motor = None
27
+ remaining_seconds = 0
28
+ last_action = "None"
29
+ motor_thread = None
30
+ stop_flag = False
31
+
32
+ # Load motors from file on startup
33
+ def load_motors():
34
+ """Load motors from motors.json file"""
35
+ global motors
36
+ try:
37
+ if os.path.exists(MOTORS_FILE):
38
+ with open(MOTORS_FILE, 'r') as f:
39
+ motors = json.load(f)
40
+ print(f"[LOAD] Loaded {len(motors)} motors from {MOTORS_FILE}")
41
+ for name, gpio in motors.items():
42
+ print(f" - {name}: GPIO {gpio}")
43
+ else:
44
+ print(f"[LOAD] No {MOTORS_FILE} found, starting with empty motors")
45
+ except Exception as e:
46
+ print(f"[ERROR] Failed to load motors: {e}")
47
+ motors = {}
48
+
49
+ def save_motors():
50
+ """Save motors to motors.json file"""
51
+ try:
52
+ with open(MOTORS_FILE, 'w') as f:
53
+ json.dump(motors, f, indent=2)
54
+ print(f"[SAVE] Motors saved to {MOTORS_FILE}")
55
+ except Exception as e:
56
+ print(f"[ERROR] Failed to save motors: {e}")
57
+
58
+ # Load motors on startup
59
+ load_motors()
60
+
61
+ # Request Models
62
+ class MotorConfig(BaseModel):
63
+ name: str
64
+ gpio: int
65
+
66
+ class UpdateMotorConfig(BaseModel):
67
+ name: str
68
+ gpio: int
69
+
70
+ class TriggerMotor(BaseModel):
71
+ motor_name: str
72
+ seconds: int = 5
73
+
74
+ class StopMotors(BaseModel):
75
+ motor_name: Optional[str] = None
76
+
77
+ class TestConfig(BaseModel):
78
+ seconds_on: int = 2
79
+ seconds_pause: int = 1
80
+ pins: Optional[List[int]] = None
81
+
82
+ # Helper Functions
83
+ def run_motor_thread(motor_name: str, duration: int):
84
+ global active_motor, remaining_seconds, last_action, stop_flag
85
+
86
+ active_motor = motor_name
87
+ remaining_seconds = duration
88
+ last_action = f"Triggered {motor_name}"
89
+
90
+ print(f"[MOTOR] Starting {motor_name} for {duration} seconds")
91
+
92
+ # Simulate motor running
93
+ for i in range(duration):
94
+ if stop_flag:
95
+ print(f"[MOTOR] {motor_name} stopped by user")
96
+ break
97
+ remaining_seconds = duration - i
98
+ time.sleep(1)
99
+
100
+ active_motor = None
101
+ remaining_seconds = 0
102
+ stop_flag = False
103
+ print(f"[MOTOR] {motor_name} completed")
104
+
105
+ # API Endpoints
106
+ @app.get("/")
107
+ async def root():
108
+ return {
109
+ "message": "Motor Control API",
110
+ "version": "1.0.0",
111
+ "endpoints": ["/status", "/test", "/add-motor", "/trigger-motor", "/list-motors", "/stop-motors"]
112
+ }
113
+
114
+ @app.get("/status")
115
+ async def get_status():
116
+ status = "running" if active_motor else "idle"
117
+ return {
118
+ "status": status,
119
+ "active_motor": active_motor,
120
+ "remaining_seconds": remaining_seconds,
121
+ "last_action": last_action,
122
+ "total_motors": len(motors)
123
+ }
124
+
125
+ @app.post("/add-motor")
126
+ async def add_motor(config: MotorConfig):
127
+ if config.name in motors:
128
+ raise HTTPException(status_code=400, detail=f"Motor '{config.name}' already exists")
129
+
130
+ motors[config.name] = {
131
+ "name": config.name,
132
+ "gpio": config.gpio
133
+ }
134
+ save_motors()
135
+
136
+ print(f"[MOTOR] Added motor: {config.name} on GPIO {config.gpio}")
137
+
138
+ return {
139
+ "status": "success",
140
+ "message": f"Motor '{config.name}' added successfully",
141
+ "motor": motors[config.name]
142
+ }
143
+
144
+ @app.get("/list-motors")
145
+ async def list_motors():
146
+ motor_list = list(motors.values())
147
+ return {
148
+ "status": "success",
149
+ "count": len(motor_list),
150
+ "motors": motor_list
151
+ }
152
+
153
+ @app.put("/update-motor/{motor_name}")
154
+ async def update_motor(motor_name: str, config: UpdateMotorConfig):
155
+ if motor_name not in motors:
156
+ raise HTTPException(status_code=404, detail=f"Motor '{motor_name}' not found")
157
+
158
+ if active_motor == motor_name:
159
+ raise HTTPException(status_code=409, detail=f"Cannot update motor '{motor_name}' while it is running")
160
+
161
+ if config.name != motor_name and config.name in motors:
162
+ raise HTTPException(status_code=400, detail=f"Motor name '{config.name}' already exists")
163
+
164
+ if config.name != motor_name:
165
+ del motors[motor_name]
166
+
167
+ motors[config.name] = {
168
+ "name": config.name,
169
+ "gpio": config.gpio
170
+ }
171
+ save_motors()
172
+
173
+ print(f"[MOTOR] Updated motor: {motor_name} -> {config.name} on GPIO {config.gpio}")
174
+
175
+ return {
176
+ "status": "success",
177
+ "message": f"Motor updated successfully",
178
+ "motor": motors[config.name]
179
+ }
180
+
181
+ @app.delete("/delete-motor/{motor_name}")
182
+ async def delete_motor(motor_name: str):
183
+ if motor_name not in motors:
184
+ raise HTTPException(status_code=404, detail=f"Motor '{motor_name}' not found")
185
+
186
+ if active_motor == motor_name:
187
+ raise HTTPException(status_code=409, detail=f"Cannot delete motor '{motor_name}' while it is running")
188
+
189
+ deleted_motor = motors.pop(motor_name)
190
+ save_motors()
191
+
192
+ print(f"[MOTOR] Deleted motor: {motor_name}")
193
+
194
+ return {
195
+ "status": "success",
196
+ "message": f"Motor '{motor_name}' deleted successfully",
197
+ "motor": deleted_motor
198
+ }
199
+
200
+ @app.post("/trigger-motor")
201
+ async def trigger_motor(config: TriggerMotor):
202
+ global motor_thread, stop_flag
203
+
204
+ if config.motor_name not in motors:
205
+ raise HTTPException(status_code=404, detail=f"Motor '{config.motor_name}' not found")
206
+
207
+ if active_motor:
208
+ raise HTTPException(status_code=409, detail=f"Motor '{active_motor}' is already running")
209
+
210
+ stop_flag = False
211
+ motor_thread = threading.Thread(target=run_motor_thread, args=(config.motor_name, config.seconds))
212
+ motor_thread.start()
213
+
214
+ return {
215
+ "status": "success",
216
+ "message": f"Motor '{config.motor_name}' triggered for {config.seconds} seconds",
217
+ "motor_name": config.motor_name,
218
+ "duration": config.seconds
219
+ }
220
+
221
+ @app.post("/stop-motors")
222
+ async def stop_motors(config: Optional[StopMotors] = None):
223
+ global stop_flag, last_action
224
+
225
+ try:
226
+ if not active_motor:
227
+ return {
228
+ "status": "success",
229
+ "message": "No motors are currently running"
230
+ }
231
+
232
+ stop_flag = True
233
+ last_action = f"Stopped {active_motor}"
234
+
235
+ # Wait for thread to finish
236
+ if motor_thread and motor_thread.is_alive():
237
+ motor_thread.join(timeout=2)
238
+
239
+ return {
240
+ "status": "success",
241
+ "message": "All motors stopped",
242
+ "stopped_motor": active_motor
243
+ }
244
+ except Exception as e:
245
+ # Handle GPIO busy or other errors gracefully
246
+ print(f"[ERROR] Stop motors failed: {e}")
247
+ stop_flag = True
248
+ return {
249
+ "status": "error",
250
+ "message": f"Error stopping motors: {str(e)}",
251
+ "detail": str(e)
252
+ }
253
+
254
+ @app.post("/test")
255
+ async def run_test(config: TestConfig):
256
+ global last_action
257
+
258
+ if active_motor:
259
+ raise HTTPException(status_code=409, detail="A motor is already running")
260
+
261
+ pins = config.pins if config.pins else list(range(1, 28))
262
+
263
+ print(f"[TEST] Running test sequence:")
264
+ print(f" - Seconds ON: {config.seconds_on}")
265
+ print(f" - Seconds Pause: {config.seconds_pause}")
266
+ print(f" - Pins: {pins}")
267
+
268
+ last_action = f"Test sequence started"
269
+
270
+ # Simulate test sequence
271
+ def test_sequence():
272
+ global last_action
273
+ for pin in pins:
274
+ last_action = f"test:gpio:{pin}"
275
+ print(f">>> TESTING GPIO {pin}")
276
+ time.sleep(config.seconds_on)
277
+ print(f"[TEST] Pin {pin} OFF, pausing {config.seconds_pause}s")
278
+ time.sleep(config.seconds_pause)
279
+ last_action = "Test sequence completed"
280
+ print("[TEST] Test sequence completed")
281
+
282
+ test_thread = threading.Thread(target=test_sequence)
283
+ test_thread.start()
284
+
285
+ return {
286
+ "status": "success",
287
+ "message": "Test sequence started",
288
+ "config": {
289
+ "seconds_on": config.seconds_on,
290
+ "seconds_pause": config.seconds_pause,
291
+ "pins": pins
292
+ }
293
+ }
294
+
295
+ if __name__ == "__main__":
296
+ import uvicorn
297
+ print("=" * 60)
298
+ print("Motor Control API Server")
299
+ print("=" * 60)
300
+ print("Starting server on http://0.0.0.0:8001")
301
+ print("API Documentation: http://localhost:8001/docs")
302
+ print("ReDoc: http://localhost:8001/redoc")
303
+ print("=" * 60)
304
+ uvicorn.run(app, host="0.0.0.0", port=8001)
@@ -0,0 +1,101 @@
1
+ # Unified Raspberry Pi Requirements for All RoboVision Servers
2
+ # Vision Server + Motor Server + Audio Server
3
+ # Install with: pip install -r requirements_pi_unified.txt
4
+
5
+ # ============================================================================
6
+ # SYSTEM DEPENDENCIES (Install these first with apt)
7
+ # ============================================================================
8
+ # sudo apt-get update
9
+ # sudo apt-get install -y python3-opencv python3-numpy python3-pil
10
+ # sudo apt-get install -y libatlas-base-dev libopenblas-dev
11
+ # sudo apt-get install -y libhdf5-dev libhdf5-serial-dev libhdf5-103
12
+ # sudo apt-get install -y libqt5gui5 libqt5widgets5 libqt5test5
13
+ # sudo apt-get install -y portaudio19-dev libsndfile1 alsa-utils
14
+ # sudo apt-get install -y bluetooth bluez bluez-tools pulseaudio pulseaudio-module-bluetooth
15
+ # sudo apt-get install -y python3-pyaudio
16
+
17
+ # ============================================================================
18
+ # WEB FRAMEWORKS
19
+ # ============================================================================
20
+ # Flask - For Vision Server
21
+ Flask==3.0.0
22
+ Flask-CORS==4.0.0
23
+
24
+ # FastAPI - For Motor Server and Audio Server
25
+ fastapi==0.104.1
26
+ uvicorn[standard]==0.24.0
27
+ python-multipart==0.0.6
28
+
29
+ # ============================================================================
30
+ # COMPUTER VISION & OBJECT DETECTION
31
+ # ============================================================================
32
+ # Note: Use system python3-opencv instead of pip opencv-python
33
+ # opencv-python - DO NOT INSTALL via pip, use system package
34
+
35
+ # NumPy - Compatible version for ARM
36
+ numpy==1.24.3
37
+
38
+ # Image processing
39
+ Pillow==10.1.0
40
+
41
+ # YOLO Object Detection (optional, for advanced vision features)
42
+ ultralytics==8.0.196
43
+
44
+ # ============================================================================
45
+ # AUDIO PROCESSING (For Audio Server)
46
+ # ============================================================================
47
+ # Note: python3-pyaudio should be installed via apt (see system dependencies above)
48
+ sounddevice==0.4.6
49
+ soundfile==0.12.1
50
+
51
+ # ============================================================================
52
+ # AI & CAPTION SERVICES
53
+ # ============================================================================
54
+ # Google Generative AI for image captioning (optional)
55
+ google-generativeai==0.3.1
56
+
57
+ # ============================================================================
58
+ # MOTOR CONTROL (For Motor Server)
59
+ # ============================================================================
60
+ # GPIO control for Raspberry Pi
61
+ lgpio==0.2.2.0
62
+
63
+ # Pydantic for data validation
64
+ pydantic==2.5.0
65
+
66
+ # ============================================================================
67
+ # NETWORKING & HTTP
68
+ # ============================================================================
69
+ # HTTP requests
70
+ requests==2.31.0
71
+
72
+ # CORS support
73
+ python-multipart==0.0.6
74
+
75
+ # ============================================================================
76
+ # UTILITIES
77
+ # ============================================================================
78
+ # Python dotenv for environment variables
79
+ python-dotenv==1.0.0
80
+
81
+ # ============================================================================
82
+ # OPTIONAL DEPENDENCIES
83
+ # ============================================================================
84
+ # Ngrok for tunneling (optional, for remote access)
85
+ # pyngrok==7.0.1
86
+
87
+ # Pi Camera Module support (optional, only if using Pi Camera Module)
88
+ # picamera2
89
+
90
+ # ============================================================================
91
+ # NOTES
92
+ # ============================================================================
93
+ # 1. Always install system dependencies FIRST before pip packages
94
+ # 2. Use system python3-opencv instead of pip opencv-python (it's pre-built for ARM)
95
+ # 3. Create venv with --system-site-packages to access system opencv:
96
+ # python3 -m venv venv --system-site-packages
97
+ # 4. For Bluetooth audio, make sure to pair devices first
98
+ # 5. For GPIO motor control, user must be in 'gpio' group:
99
+ # sudo usermod -a -G gpio $USER
100
+ # 6. For Bluetooth, user must be in 'bluetooth' group:
101
+ # sudo usermod -a -G bluetooth $USER
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ STATE_DIR="$ROOT_DIR/.run"
6
+ LOG_DIR="$STATE_DIR/logs"
7
+ VENV_DIR="$ROOT_DIR/venv"
8
+
9
+ mkdir -p "$LOG_DIR"
10
+
11
+ if [ -f "$ROOT_DIR/.env" ]; then
12
+ set -a
13
+ source "$ROOT_DIR/.env"
14
+ set +a
15
+ fi
16
+
17
+ source "$ROOT_DIR/services/services.sh"
18
+
19
+ require_venv() {
20
+ if [ ! -f "$VENV_DIR/bin/activate" ]; then
21
+ echo "Missing venv. Run: ./install.sh"
22
+ exit 1
23
+ fi
24
+ }
25
+
26
+ is_running() {
27
+ local name="$1"
28
+ local pid_file="$STATE_DIR/$name.pid"
29
+ if [ ! -f "$pid_file" ]; then
30
+ return 1
31
+ fi
32
+ local pid
33
+ pid="$(cat "$pid_file" 2>/dev/null || true)"
34
+ if [ -z "$pid" ]; then
35
+ return 1
36
+ fi
37
+ kill -0 "$pid" >/dev/null 2>&1
38
+ }
39
+
40
+ start_one() {
41
+ local name="$1"
42
+ local cmd_var="SERVICE_${name}_CMD"
43
+ local cmd
44
+ cmd="${!cmd_var}"
45
+ if [ -z "$cmd" ]; then
46
+ echo "Unknown service: $name"
47
+ exit 1
48
+ fi
49
+
50
+ if is_running "$name"; then
51
+ echo "$name already running"
52
+ return 0
53
+ fi
54
+
55
+ require_venv
56
+ source "$VENV_DIR/bin/activate"
57
+
58
+ local log_file="$LOG_DIR/$name.log"
59
+ local pid_file="$STATE_DIR/$name.pid"
60
+
61
+ echo "Starting $name"
62
+ nohup bash -lc "cd \"$ROOT_DIR\" && source \"$VENV_DIR/bin/activate\" && $cmd" >>"$log_file" 2>&1 &
63
+ echo $! >"$pid_file"
64
+ }
65
+
66
+ detect_terminal() {
67
+ if command -v lxterminal >/dev/null 2>&1; then
68
+ echo "lxterminal"
69
+ return 0
70
+ fi
71
+ if command -v gnome-terminal >/dev/null 2>&1; then
72
+ echo "gnome-terminal"
73
+ return 0
74
+ fi
75
+ if command -v xterm >/dev/null 2>&1; then
76
+ echo "xterm"
77
+ return 0
78
+ fi
79
+ return 1
80
+ }
81
+
82
+ start_one_term() {
83
+ local name="$1"
84
+ local cmd_var="SERVICE_${name}_CMD"
85
+ local cmd
86
+ cmd="${!cmd_var}"
87
+ if [ -z "$cmd" ]; then
88
+ echo "Unknown service: $name"
89
+ exit 1
90
+ fi
91
+
92
+ if is_running "$name"; then
93
+ echo "$name already running"
94
+ return 0
95
+ fi
96
+
97
+ require_venv
98
+
99
+ local term
100
+ term="$(detect_terminal || true)"
101
+ if [ -z "$term" ]; then
102
+ echo "No supported terminal found (need lxterminal, gnome-terminal, or xterm)"
103
+ exit 1
104
+ fi
105
+
106
+ local log_file="$LOG_DIR/$name.log"
107
+ local pid_file="$STATE_DIR/$name.pid"
108
+ local title="RoboVision:$name"
109
+ local run_cmd
110
+ run_cmd="cd \"$ROOT_DIR\" && source \"$VENV_DIR/bin/activate\" && exec $cmd"
111
+
112
+ echo "Starting $name in terminal ($term)"
113
+
114
+ if [ "$term" = "lxterminal" ]; then
115
+ lxterminal --title="$title" --command="bash -lc '$run_cmd'" >>"$log_file" 2>&1 &
116
+ echo $! >"$pid_file"
117
+ return 0
118
+ fi
119
+
120
+ if [ "$term" = "gnome-terminal" ]; then
121
+ gnome-terminal --title="$title" -- bash -lc "$run_cmd" >>"$log_file" 2>&1 &
122
+ echo $! >"$pid_file"
123
+ return 0
124
+ fi
125
+
126
+ if [ "$term" = "xterm" ]; then
127
+ xterm -T "$title" -e bash -lc "$run_cmd" >>"$log_file" 2>&1 &
128
+ echo $! >"$pid_file"
129
+ return 0
130
+ fi
131
+ }
132
+
133
+ stop_one() {
134
+ local name="$1"
135
+ local pid_file="$STATE_DIR/$name.pid"
136
+
137
+ if ! is_running "$name"; then
138
+ rm -f "$pid_file"
139
+ echo "$name not running"
140
+ return 0
141
+ fi
142
+
143
+ local pid
144
+ pid="$(cat "$pid_file")"
145
+ echo "Stopping $name (pid $pid)"
146
+ kill "$pid" >/dev/null 2>&1 || true
147
+
148
+ local i
149
+ for i in $(seq 1 30); do
150
+ if kill -0 "$pid" >/dev/null 2>&1; then
151
+ sleep 0.2
152
+ else
153
+ break
154
+ fi
155
+ done
156
+
157
+ if kill -0 "$pid" >/dev/null 2>&1; then
158
+ kill -9 "$pid" >/dev/null 2>&1 || true
159
+ fi
160
+
161
+ rm -f "$pid_file"
162
+ }
163
+
164
+ status_one() {
165
+ local name="$1"
166
+ local port_var="SERVICE_${name}_PORT"
167
+ local port
168
+ port="${!port_var}"
169
+
170
+ if is_running "$name"; then
171
+ echo "$name: running (port ${port:-n/a})"
172
+ else
173
+ echo "$name: stopped"
174
+ fi
175
+ }
176
+
177
+ tail_one() {
178
+ local name="$1"
179
+ local log_file="$LOG_DIR/$name.log"
180
+ if [ ! -f "$log_file" ]; then
181
+ echo "No log file: $log_file"
182
+ exit 1
183
+ fi
184
+ tail -n 200 -f "$log_file"
185
+ }
186
+
187
+ usage() {
188
+ echo "Usage: ./run.sh <start|start-term|stop|restart|status|logs> [service]"
189
+ echo "Services: ${SERVICES[*]}"
190
+ }
191
+
192
+ ACTION="${1:-}"
193
+ NAME="${2:-all}"
194
+
195
+ case "$ACTION" in
196
+ start)
197
+ if [ "$NAME" = "all" ]; then
198
+ for s in "${SERVICES[@]}"; do start_one "$s"; done
199
+ else
200
+ start_one "$NAME"
201
+ fi
202
+ ;;
203
+ start-term)
204
+ if [ "$NAME" = "all" ]; then
205
+ for s in "${SERVICES[@]}"; do start_one_term "$s"; done
206
+ else
207
+ start_one_term "$NAME"
208
+ fi
209
+ ;;
210
+ stop)
211
+ if [ "$NAME" = "all" ]; then
212
+ for s in "${SERVICES[@]}"; do stop_one "$s"; done
213
+ else
214
+ stop_one "$NAME"
215
+ fi
216
+ ;;
217
+ restart)
218
+ if [ "$NAME" = "all" ]; then
219
+ for s in "${SERVICES[@]}"; do stop_one "$s"; done
220
+ for s in "${SERVICES[@]}"; do start_one "$s"; done
221
+ else
222
+ stop_one "$NAME"
223
+ start_one "$NAME"
224
+ fi
225
+ ;;
226
+ status)
227
+ if [ "$NAME" = "all" ]; then
228
+ for s in "${SERVICES[@]}"; do status_one "$s"; done
229
+ else
230
+ status_one "$NAME"
231
+ fi
232
+ ;;
233
+ logs)
234
+ if [ "$NAME" = "all" ]; then
235
+ echo "Specify a service for logs"
236
+ exit 1
237
+ fi
238
+ tail_one "$NAME"
239
+ ;;
240
+ *)
241
+ usage
242
+ exit 1
243
+ ;;
244
+ esac
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+
3
+ SERVICES=("vision" "motor" "audio")
4
+
5
+ SERVICE_vision_PORT=5000
6
+ SERVICE_vision_CMD="python app_pi_clean.py"
7
+
8
+ SERVICE_motor_PORT=8001
9
+ SERVICE_motor_CMD="python motor_server.py"
10
+
11
+ SERVICE_audio_PORT=8000
12
+ SERVICE_audio_CMD="python audio_server_pi.py"