Oliver Nitsche Claude Sonnet 4.6 commited on
Commit
ee7b214
Β·
1 Parent(s): 897c12b

Add GREETING state so recognised faces are shown in GUI and spoken

Browse files

Previously, a matched face triggered speak() β†’ goto_sleep() inline
inside the ACTIVE state, so the GUI state jumped straight to "sleeping"
and the user saw nothing.

Changes:
- New State.GREETING: entered as soon as a face is matched; sets
_shared["recognized_name"] so the GUI shows the name immediately,
before speak() is even called. If TTS is unavailable the name is
still visible in the control panel.
- /status now returns recognized_name alongside state.
- GUI: new green greeting-section div shows "Welcome back, <name>!"
when state is "greeting"; hidden in all other states.
- STATE_LABELS updated with greeting entry.
- Greeting section styled in style.css (green card).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

recognizer/main.py CHANGED
@@ -32,6 +32,7 @@ class State(Enum):
32
  SLEEPING = auto()
33
  WAKING = auto()
34
  ACTIVE = auto()
 
35
  ENROLLING = auto()
36
 
37
 
@@ -44,7 +45,8 @@ class Recognizer(ReachyMiniApp):
44
  _lock = threading.Lock()
45
  _shared: dict = {
46
  "state": "sleeping",
47
- "pending_name": None, # set by /set_name when ENROLLING
 
48
  }
49
 
50
  # --- Settings-app REST endpoints ---
@@ -61,7 +63,10 @@ class Recognizer(ReachyMiniApp):
61
  @self.settings_app.get("/status")
62
  def get_status():
63
  with _lock:
64
- return {"state": _shared["state"]}
 
 
 
65
 
66
  # --- Initialise ---
67
  face_db = load_face_db()
@@ -71,6 +76,7 @@ class Recognizer(ReachyMiniApp):
71
  active_start = 0.0
72
  last_face_check = 0.0
73
  enrollment_frames: list[np.ndarray] = []
 
74
  scan_t0 = 0.0 # reference time for head-scan idle animation
75
 
76
  reachy_mini.goto_sleep()
@@ -134,9 +140,10 @@ class Recognizer(ReachyMiniApp):
134
  try:
135
  name = find_match(frame, face_db)
136
  if name:
137
- speak(f"Hi {name}!", reachy_mini)
138
- reachy_mini.goto_sleep()
139
- state = State.SLEEPING
 
140
  else:
141
  speak(
142
  "I don't know you yet. "
@@ -157,6 +164,18 @@ class Recognizer(ReachyMiniApp):
157
 
158
  time.sleep(0.05)
159
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  # ---------- ENROLLING ----------
161
  elif state == State.ENROLLING:
162
  with _lock:
 
32
  SLEEPING = auto()
33
  WAKING = auto()
34
  ACTIVE = auto()
35
+ GREETING = auto() # known face found – show name in GUI, speak, then sleep
36
  ENROLLING = auto()
37
 
38
 
 
45
  _lock = threading.Lock()
46
  _shared: dict = {
47
  "state": "sleeping",
48
+ "pending_name": None, # set by /set_name when ENROLLING
49
+ "recognized_name": None, # set when a known face is found
50
  }
51
 
52
  # --- Settings-app REST endpoints ---
 
63
  @self.settings_app.get("/status")
64
  def get_status():
65
  with _lock:
66
+ return {
67
+ "state": _shared["state"],
68
+ "recognized_name": _shared["recognized_name"],
69
+ }
70
 
71
  # --- Initialise ---
72
  face_db = load_face_db()
 
76
  active_start = 0.0
77
  last_face_check = 0.0
78
  enrollment_frames: list[np.ndarray] = []
79
+ greeting_name = ""
80
  scan_t0 = 0.0 # reference time for head-scan idle animation
81
 
82
  reachy_mini.goto_sleep()
 
140
  try:
141
  name = find_match(frame, face_db)
142
  if name:
143
+ greeting_name = name
144
+ with _lock:
145
+ _shared["recognized_name"] = name
146
+ state = State.GREETING
147
  else:
148
  speak(
149
  "I don't know you yet. "
 
164
 
165
  time.sleep(0.05)
166
 
167
+ # ---------- GREETING ----------
168
+ elif state == State.GREETING:
169
+ with _lock:
170
+ _shared["state"] = "greeting"
171
+ # Speak first; GUI already shows the name via /status.
172
+ # If TTS is unavailable the name remains visible in the UI.
173
+ speak(f"Hi {greeting_name}!", reachy_mini)
174
+ reachy_mini.goto_sleep()
175
+ with _lock:
176
+ _shared["recognized_name"] = None
177
+ state = State.SLEEPING
178
+
179
  # ---------- ENROLLING ----------
180
  elif state == State.ENROLLING:
181
  with _lock:
recognizer/static/index.html CHANGED
@@ -15,6 +15,10 @@
15
  State: <strong id="state-label">β€”</strong>
16
  </div>
17
 
 
 
 
 
18
  <div id="enroll-section" style="display:none;">
19
  <p>A new face was detected. Enter the person's name:</p>
20
  <div id="enroll-form">
 
15
  State: <strong id="state-label">β€”</strong>
16
  </div>
17
 
18
+ <div id="greeting-section" style="display:none;">
19
+ <p>Welcome back, <strong id="greeting-name"></strong>! πŸ‘‹</p>
20
+ </div>
21
+
22
  <div id="enroll-section" style="display:none;">
23
  <p>A new face was detected. Enter the person's name:</p>
24
  <div id="enroll-form">
recognizer/static/main.js CHANGED
@@ -2,6 +2,7 @@ const STATE_LABELS = {
2
  sleeping: "😴 Sleeping – listening for noise",
3
  waking: "⚑ Waking up…",
4
  active: "πŸ‘€ Active – looking for faces",
 
5
  enrolling: "πŸ†• New face detected – waiting for name",
6
  };
7
 
@@ -19,6 +20,16 @@ async function pollStatus() {
19
  const label = document.getElementById("state-label");
20
  label.textContent = STATE_LABELS[newState] ?? newState;
21
 
 
 
 
 
 
 
 
 
 
 
22
  const enrollSection = document.getElementById("enroll-section");
23
  enrollSection.style.display = newState === "enrolling" ? "block" : "none";
24
 
 
2
  sleeping: "😴 Sleeping – listening for noise",
3
  waking: "⚑ Waking up…",
4
  active: "πŸ‘€ Active – looking for faces",
5
+ greeting: "πŸ‘‹ Face recognised!",
6
  enrolling: "πŸ†• New face detected – waiting for name",
7
  };
8
 
 
20
  const label = document.getElementById("state-label");
21
  label.textContent = STATE_LABELS[newState] ?? newState;
22
 
23
+ // Greeting section: show recognised name
24
+ const greetingSection = document.getElementById("greeting-section");
25
+ if (newState === "greeting" && data.recognized_name) {
26
+ document.getElementById("greeting-name").textContent = data.recognized_name;
27
+ greetingSection.style.display = "block";
28
+ } else {
29
+ greetingSection.style.display = "none";
30
+ }
31
+
32
+ // Enroll section
33
  const enrollSection = document.getElementById("enroll-section");
34
  enrollSection.style.display = newState === "enrolling" ? "block" : "none";
35
 
recognizer/static/style.css CHANGED
@@ -28,6 +28,17 @@ h1 {
28
  color: #1a73e8;
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
31
  #enroll-section {
32
  background: #fff;
33
  border: 1px solid #fbc02d;
 
28
  color: #1a73e8;
29
  }
30
 
31
+ #greeting-section {
32
+ background: #e8f5e9;
33
+ border: 1px solid #66bb6a;
34
+ border-radius: 8px;
35
+ padding: 1.25rem;
36
+ margin-bottom: 1.5rem;
37
+ font-size: 1.1rem;
38
+ font-weight: 500;
39
+ color: #1b5e20;
40
+ }
41
+
42
  #enroll-section {
43
  background: #fff;
44
  border: 1px solid #fbc02d;