devvibes commited on
Commit
fc0ceaf
Β·
1 Parent(s): ca43655

Add demo mode - robot animations without Streamlit

Browse files
Files changed (1) hide show
  1. haven_kitchen_os/main.py +173 -54
haven_kitchen_os/main.py CHANGED
@@ -4,19 +4,16 @@ Main entry point that integrates with the Reachy Mini daemon.
4
 
5
  This module provides the ReachyMiniApp wrapper that:
6
  1. Receives the robot instance from the daemon
7
- 2. Launches the Streamlit UI
8
- 3. Makes the robot available to the animation system
9
- 4. Handles graceful shutdown
10
  """
11
 
12
  import threading
13
- import subprocess
14
- import sys
15
- import os
16
  import time
17
- import signal
18
 
19
  from reachy_mini import ReachyMini, ReachyMiniApp
 
20
 
21
  # Module-level robot instance (shared with animations.py)
22
  _shared_robot: ReachyMini | None = None
@@ -36,6 +33,30 @@ def set_shared_robot(robot: ReachyMini | None) -> None:
36
  _shared_robot = robot
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  class HavenKitchenOs(ReachyMiniApp):
40
  """
41
  Haven Kitchen OS - AI Home Management System
@@ -45,70 +66,168 @@ class HavenKitchenOs(ReachyMiniApp):
45
  - πŸ‘©πŸ»β€πŸ³ Brie: Enthusiastic personal chef (amber eyes)
46
  """
47
 
48
- # URL to the Streamlit UI - displayed in the dashboard
49
- custom_app_url: str | None = "http://localhost:8502"
50
-
51
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
52
  """
53
  Main entry point called by the Reachy Mini daemon.
54
-
55
- Args:
56
- reachy_mini: The robot instance (already connected)
57
- stop_event: Event to signal when the app should stop
58
  """
59
- # Share the robot instance with the animation system
60
  set_shared_robot(reachy_mini)
61
 
62
- # Get the directory where this file lives
63
- app_dir = os.path.dirname(os.path.abspath(__file__))
64
- app_path = os.path.join(app_dir, "app.py")
65
-
66
- # Launch Streamlit as a subprocess
67
- streamlit_process = subprocess.Popen(
68
- [
69
- sys.executable, "-m", "streamlit", "run",
70
- app_path,
71
- "--server.port", "8502",
72
- "--server.headless", "true",
73
- "--server.runOnSave", "false",
74
- "--browser.gatherUsageStats", "false",
75
- ],
76
- cwd=app_dir,
77
- stdout=subprocess.PIPE,
78
- stderr=subprocess.PIPE,
79
- )
80
-
81
  print("🌿 Haven Kitchen OS started!")
82
  print(" πŸ‘©πŸ»β€πŸ’Ό Olivia and πŸ‘©πŸ»β€πŸ³ Brie are ready to help.")
83
- print(f" 🌐 Open: {self.custom_app_url}")
84
 
85
  try:
86
- # Keep running until stop_event is set
87
- while not stop_event.is_set():
88
- # Check if Streamlit is still running
89
- if streamlit_process.poll() is not None:
90
- print("⚠️ Streamlit process ended unexpectedly")
91
- break
92
-
93
- # Small sleep to prevent busy-waiting
94
- time.sleep(0.5)
95
 
96
  finally:
97
- # Clean up: terminate Streamlit
98
  print("πŸ›‘ Shutting down Haven Kitchen OS...")
99
-
100
- # Clear the shared robot
101
  set_shared_robot(None)
102
 
103
- # Terminate Streamlit gracefully
104
- if streamlit_process.poll() is None:
105
- streamlit_process.terminate()
106
- try:
107
- streamlit_process.wait(timeout=5)
108
- except subprocess.TimeoutExpired:
109
- streamlit_process.kill()
 
 
 
110
 
111
  print("πŸ‘‹ Haven Kitchen OS stopped. Goodbye!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
 
114
  # Allow running directly for testing
 
4
 
5
  This module provides the ReachyMiniApp wrapper that:
6
  1. Receives the robot instance from the daemon
7
+ 2. Runs a demo showcasing Olivia and Brie personas
8
+ 3. Handles graceful shutdown
 
9
  """
10
 
11
  import threading
 
 
 
12
  import time
13
+ import random
14
 
15
  from reachy_mini import ReachyMini, ReachyMiniApp
16
+ from reachy_mini.utils import create_head_pose
17
 
18
  # Module-level robot instance (shared with animations.py)
19
  _shared_robot: ReachyMini | None = None
 
33
  _shared_robot = robot
34
 
35
 
36
+ # ============================================
37
+ # 🎨 PERSONA EYE COLORS
38
+ # ============================================
39
+ COLORS = {
40
+ "Olivia": [0, 180, 80], # Sage green - calm, professional
41
+ "Brie": [255, 160, 0], # Warm amber - enthusiastic, friendly
42
+ "neutral": [100, 100, 100], # Soft white/gray - idle
43
+ "celebration": [255, 220, 0], # Bright gold - success!
44
+ }
45
+
46
+
47
+ def set_eye_color(robot, color_name):
48
+ """Set eye color by name."""
49
+ try:
50
+ rgb = COLORS.get(color_name, COLORS["neutral"])
51
+ if hasattr(robot, 'head'):
52
+ if hasattr(robot.head, 'l_eye') and hasattr(robot.head.l_eye, 'set_color'):
53
+ robot.head.l_eye.set_color(rgb)
54
+ if hasattr(robot.head, 'r_eye') and hasattr(robot.head.r_eye, 'set_color'):
55
+ robot.head.r_eye.set_color(rgb)
56
+ except Exception:
57
+ pass
58
+
59
+
60
  class HavenKitchenOs(ReachyMiniApp):
61
  """
62
  Haven Kitchen OS - AI Home Management System
 
66
  - πŸ‘©πŸ»β€πŸ³ Brie: Enthusiastic personal chef (amber eyes)
67
  """
68
 
 
 
 
69
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
70
  """
71
  Main entry point called by the Reachy Mini daemon.
72
+ Runs an interactive demo showcasing both personas.
 
 
 
73
  """
74
+ # Share the robot instance
75
  set_shared_robot(reachy_mini)
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  print("🌿 Haven Kitchen OS started!")
78
  print(" πŸ‘©πŸ»β€πŸ’Ό Olivia and πŸ‘©πŸ»β€πŸ³ Brie are ready to help.")
 
79
 
80
  try:
81
+ # Run the demo loop
82
+ self._run_demo(reachy_mini, stop_event)
 
 
 
 
 
 
 
83
 
84
  finally:
85
+ # Clean up
86
  print("πŸ›‘ Shutting down Haven Kitchen OS...")
 
 
87
  set_shared_robot(None)
88
 
89
+ # Return to neutral position
90
+ try:
91
+ set_eye_color(reachy_mini, "neutral")
92
+ reachy_mini.goto_target(
93
+ head=create_head_pose(),
94
+ antennas=[0, 0],
95
+ duration=0.5
96
+ )
97
+ except:
98
+ pass
99
 
100
  print("πŸ‘‹ Haven Kitchen OS stopped. Goodbye!")
101
+
102
+ def _run_demo(self, robot: ReachyMini, stop_event: threading.Event):
103
+ """Run the demo loop showcasing Olivia and Brie."""
104
+
105
+ while not stop_event.is_set():
106
+ # ========== OLIVIA'S TURN ==========
107
+ print("\nπŸ‘©πŸ»β€πŸ’Ό Olivia: Good morning! Let me help organize your day...")
108
+
109
+ # Olivia greeting - warm and professional
110
+ set_eye_color(robot, "Olivia")
111
+ robot.goto_target(head=create_head_pose(roll=8, z=5), duration=0.4)
112
+ time.sleep(0.3)
113
+
114
+ # Gentle nod
115
+ robot.goto_target(head=create_head_pose(z=15), duration=0.2)
116
+ time.sleep(0.15)
117
+ robot.goto_target(head=create_head_pose(z=-5), duration=0.15)
118
+ time.sleep(0.1)
119
+ robot.goto_target(head=create_head_pose(z=0), duration=0.2)
120
+
121
+ if stop_event.is_set():
122
+ break
123
+
124
+ time.sleep(1.0)
125
+
126
+ # Olivia thinking
127
+ print("πŸ‘©πŸ»β€πŸ’Ό Olivia: Let me check your schedule...")
128
+ robot.goto_target(head=create_head_pose(yaw=15, z=20, roll=5), duration=0.5)
129
+ time.sleep(1.0)
130
+ robot.goto_target(head=create_head_pose(yaw=0, z=0, roll=0), duration=0.4)
131
+
132
+ if stop_event.is_set():
133
+ break
134
+
135
+ time.sleep(1.5)
136
+
137
+ # ========== HANDOFF TO BRIE ==========
138
+ print("\nπŸ‘©πŸ»β€πŸ’Ό Olivia: Time for dinner planning! Let me get Brie...")
139
+
140
+ # Look toward Brie
141
+ robot.goto_target(head=create_head_pose(yaw=30, z=-5), duration=0.4)
142
+ time.sleep(0.3)
143
+
144
+ # Nod to Brie
145
+ robot.goto_target(head=create_head_pose(z=10), duration=0.2)
146
+ time.sleep(0.15)
147
+ robot.goto_target(head=create_head_pose(z=0), duration=0.2)
148
+
149
+ # Fade to Brie's color
150
+ set_eye_color(robot, "Brie")
151
+ robot.goto_target(head=create_head_pose(yaw=0, z=0), duration=0.4)
152
+
153
+ if stop_event.is_set():
154
+ break
155
+
156
+ time.sleep(1.0)
157
+
158
+ # ========== BRIE'S TURN ==========
159
+ print("\nπŸ‘©πŸ»β€πŸ³ Brie: Ooh, dinner time! I'm SO excited to cook with you!")
160
+
161
+ # Brie excited greeting
162
+ robot.goto_target(antennas=[0.4, 0.4], duration=0.2)
163
+ time.sleep(0.3)
164
+ robot.goto_target(head=create_head_pose(z=15), duration=0.2)
165
+
166
+ # Antenna wiggle!
167
+ for _ in range(2):
168
+ robot.goto_target(antennas=[0.5, -0.5], duration=0.12)
169
+ time.sleep(0.12)
170
+ robot.goto_target(antennas=[-0.5, 0.5], duration=0.12)
171
+ time.sleep(0.12)
172
+ robot.goto_target(antennas=[0, 0], duration=0.2)
173
+ robot.goto_target(head=create_head_pose(z=0), duration=0.2)
174
+
175
+ if stop_event.is_set():
176
+ break
177
+
178
+ time.sleep(1.0)
179
+
180
+ # Brie thinking about recipe
181
+ print("πŸ‘©πŸ»β€πŸ³ Brie: Hmm, what shall we make tonight?")
182
+ robot.goto_target(head=create_head_pose(z=25, yaw=10), antennas=[0.2, -0.1], duration=0.4)
183
+ time.sleep(1.0)
184
+
185
+ # Looking around for ingredients
186
+ robot.goto_target(head=create_head_pose(yaw=25, z=10), duration=0.4)
187
+ time.sleep(0.3)
188
+ robot.goto_target(head=create_head_pose(yaw=-25, z=5), duration=0.5)
189
+ time.sleep(0.3)
190
+ robot.goto_target(head=create_head_pose(yaw=0, z=0), duration=0.3)
191
+
192
+ if stop_event.is_set():
193
+ break
194
+
195
+ time.sleep(0.5)
196
+
197
+ # Brie has an idea!
198
+ print("πŸ‘©πŸ»β€πŸ³ Brie: I know! Let's make pasta carbonara! 🍝")
199
+ set_eye_color(robot, "celebration")
200
+ robot.goto_target(head=create_head_pose(z=25), antennas=[0.7, 0.7], duration=0.2)
201
+ time.sleep(0.15)
202
+
203
+ # Celebration wiggle
204
+ for _ in range(3):
205
+ robot.goto_target(antennas=[0.5, -0.5], duration=0.1)
206
+ time.sleep(0.1)
207
+ robot.goto_target(antennas=[-0.5, 0.5], duration=0.1)
208
+ time.sleep(0.1)
209
+
210
+ robot.goto_target(head=create_head_pose(z=0), antennas=[0, 0], duration=0.3)
211
+ set_eye_color(robot, "Brie")
212
+
213
+ if stop_event.is_set():
214
+ break
215
+
216
+ time.sleep(2.0)
217
+
218
+ # ========== BACK TO OLIVIA ==========
219
+ print("\nπŸ‘©πŸ»β€πŸ³ Brie: Great! I'll hand you back to Olivia now.")
220
+ set_eye_color(robot, "Olivia")
221
+
222
+ # Reset to neutral
223
+ robot.goto_target(head=create_head_pose(), antennas=[0, 0], duration=0.5)
224
+
225
+ if stop_event.is_set():
226
+ break
227
+
228
+ time.sleep(3.0)
229
+
230
+ print("\n--- Demo loop restarting ---\n")
231
 
232
 
233
  # Allow running directly for testing