Skip to content

Commit 00d0daa

Browse files
committed
Initial commit: Kevin (squashed)
0 parents  commit 00d0daa

File tree

11 files changed

+782
-0
lines changed

11 files changed

+782
-0
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPENAI_API_KEY=sk-REPLACE_WITH_YOUR_KEY

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
lint:
11+
runs-on: windows-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Set up Python
15+
uses: actions/setup-python@v4
16+
with:
17+
python-version: '3.11'
18+
- name: Install dependencies
19+
run: |
20+
python -m pip install --upgrade pip
21+
pip install flake8
22+
- name: Run flake8
23+
run: |
24+
flake8 . --max-line-length=120

.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Python
2+
__pycache__/
3+
*.pyc
4+
*.pyo
5+
*.pyd
6+
env/
7+
venv/
8+
.venv/
9+
pyvenv.cfg
10+
*.egg-info/
11+
.eggs/
12+
.env
13+
openai_diag.log
14+
15+
# IDEs
16+
.vscode/
17+
.idea/
18+
19+
# macOS
20+
.DS_Store
21+
22+
# Arduino
23+
build/
24+
*.hex
25+
26+
# logs
27+
*.log

CONTRIBUTING.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Contributing
2+
============
3+
4+
Thanks for wanting to help improve Kevin!
5+
6+
- Keep secrets out of the repo (.env, keys, logs).
7+
- Add tests or example snippets in `tests/` if you change behaviour.
8+
- Send a PR and include a short description of the change.
9+
10+
Development workflow
11+
1. Create a feature branch
12+
2. Make changes, run tests
13+
3. Open a Pull Request against `main`

Deepseek_config

Whitespace-only changes.

Intelligence_part/brain_kevin.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import os
2+
import openai
3+
import speech_recognition as sr
4+
import cv2
5+
import serial
6+
import time
7+
import logging
8+
import traceback
9+
import pyttsx3 # For TTS (robot "speaking")
10+
11+
# Config
12+
# Prefer reading the OpenAI API key from the environment for safety.
13+
openai.api_key = os.getenv("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")
14+
if not openai.api_key:
15+
print("Warning: OPENAI_API_KEY is not set in environment. Set it to use OpenAI services.")
16+
17+
# Arduino serial port (updated to COM6 as provided)
18+
serial_port = 'COM6' # Check in Arduino IDE (e.g., COM6 on Windows)
19+
baud_rate = 9600
20+
webcam_id = 0 # Webcam index
21+
22+
# Retry / timeout settings
23+
serial_retry_attempts = 5
24+
serial_retry_delay = 2.0 # seconds between attempts
25+
26+
# Webcam preview toggle
27+
enable_preview = False
28+
29+
# Initialize
30+
ser = None
31+
for attempt in range(1, serial_retry_attempts + 1):
32+
try:
33+
ser = serial.Serial(serial_port, baud_rate, timeout=1)
34+
time.sleep(2) # Wait for Arduino connection
35+
print(f"Connected to Arduino on {serial_port}")
36+
break
37+
except Exception as e:
38+
print(f"Attempt {attempt}/{serial_retry_attempts}: could not open serial port {serial_port}: {e}")
39+
ser = None
40+
if attempt < serial_retry_attempts:
41+
time.sleep(serial_retry_delay)
42+
43+
recognizer = sr.Recognizer()
44+
mic = None
45+
try:
46+
mic = sr.Microphone()
47+
except Exception as e:
48+
print(f"Warning: microphone not available: {e}")
49+
50+
engine = pyttsx3.init() # TTS
51+
# Configure TTS engine for Windows (SAPI5) where possible
52+
try:
53+
voices = engine.getProperty('voices')
54+
# pick a default voice (0) if available
55+
if voices:
56+
engine.setProperty('voice', voices[0].id)
57+
engine.setProperty('rate', 170)
58+
engine.setProperty('volume', 1.0)
59+
except Exception as e:
60+
print(f"Warning: could not configure TTS engine: {e}")
61+
62+
def speak(text):
63+
"""Speak text through system speakers. Falls back to winsound playback on Windows if pyttsx3 fails."""
64+
try:
65+
engine.say(text)
66+
engine.runAndWait()
67+
return
68+
except Exception as e:
69+
print(f"TTS engine failed, attempting fallback: {e}")
70+
71+
# Fallback: synthesize to WAV and play via winsound (Windows only)
72+
try:
73+
import tempfile
74+
import sys
75+
if sys.platform.startswith('win'):
76+
import wave
77+
import os
78+
import pyttsx3
79+
fd, path = tempfile.mkstemp(suffix='.wav')
80+
os.close(fd)
81+
# Try to save via pyttsx3 engine (may not work on some systems)
82+
try:
83+
engine.save_to_file(text, path)
84+
engine.runAndWait()
85+
import winsound
86+
winsound.PlaySound(path, winsound.SND_FILENAME)
87+
os.remove(path)
88+
return
89+
except Exception as e2:
90+
print(f"Fallback TTS save/play failed: {e2}")
91+
except Exception as e:
92+
print(f"TTS fallback not available: {e}")
93+
94+
# Face tracking cascade (use built-in haarcascade)
95+
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
96+
cap = None
97+
try:
98+
cap = cv2.VideoCapture(webcam_id)
99+
if not cap.isOpened():
100+
print(f"Warning: could not open webcam id {webcam_id}")
101+
cap.release()
102+
cap = None
103+
else:
104+
print(f"Webcam {webcam_id} opened")
105+
if enable_preview:
106+
cv2.namedWindow('Webcam Preview', cv2.WINDOW_NORMAL)
107+
except Exception as e:
108+
print(f"Warning: webcam init failed: {e}")
109+
cap = None
110+
111+
# Robot identity / wake-word
112+
robot_name = "Kevin"
113+
114+
def track_face(frame):
115+
if frame is None:
116+
return None, None
117+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
118+
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
119+
if len(faces) > 0:
120+
(x, y, w, h) = faces[0]
121+
fx = x + w // 2
122+
fy = y + h // 2
123+
return fx, fy, (x, y, w, h)
124+
return None, None, None
125+
126+
def send_command(cmd):
127+
if ser:
128+
try:
129+
ser.write((cmd + '\n').encode())
130+
print(f"Sent: {cmd}")
131+
except Exception as e:
132+
print(f"Warning: failed to send command over serial: {e}")
133+
else:
134+
print(f"Serial unavailable - would send: {cmd}")
135+
136+
def get_emotion_from_response(response):
137+
# Simple mapping; improve with ChatGPT prompt
138+
if "happy" in response.lower():
139+
return "HAPPY"
140+
elif "sad" in response.lower():
141+
return "SAD"
142+
elif "angry" in response.lower():
143+
return "ANGRY"
144+
elif "excited" in response.lower():
145+
return "EXCITED"
146+
return "DEFAULT"
147+
148+
# Main loop
149+
try:
150+
while True:
151+
frame = None
152+
if cap:
153+
ret, frame = cap.read()
154+
if not ret:
155+
frame = None
156+
157+
# Face tracking and preview overlay
158+
fx = fy = None
159+
face_rect = None
160+
if frame is not None:
161+
fx, fy, face_rect = track_face(frame)
162+
if face_rect is not None:
163+
(x, y, w, h) = face_rect
164+
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
165+
cv2.circle(frame, (fx, fy), 4, (0, 0, 255), -1)
166+
send_command(f"TRACK|{fx},{fy}")
167+
168+
if enable_preview:
169+
cv2.imshow('Webcam Preview', frame)
170+
# If user presses 'q' in the preview window, exit
171+
if cv2.waitKey(1) & 0xFF == ord('q'):
172+
print('Preview quit requested')
173+
break
174+
175+
# Speech recognition (guarded)
176+
user_input = None
177+
if mic:
178+
try:
179+
with mic as source:
180+
recognizer.adjust_for_ambient_noise(source, duration=0.5)
181+
# timeout prevents blocking indefinitely; phrase_time_limit bounds recording length
182+
audio = recognizer.listen(source, timeout=5, phrase_time_limit=8)
183+
try:
184+
if openai.api_key:
185+
user_input = recognizer.recognize_whisper(audio, model="base")
186+
else:
187+
# fallback to Google recognizer if OpenAI key not set
188+
user_input = recognizer.recognize_google(audio)
189+
print(f"You said: {user_input}")
190+
except Exception as e:
191+
print(f"Speech not recognized: {e}")
192+
except Exception as e:
193+
print(f"Microphone listen failed or timed out: {e}")
194+
195+
if not user_input:
196+
# nothing heard this loop; small sleep to avoid busy loop
197+
time.sleep(0.1)
198+
continue
199+
200+
# If user addressed the robot by name, respond locally and skip OpenAI
201+
response = None
202+
lowered = user_input.lower() if user_input else ""
203+
if any(phrase in lowered for phrase in [robot_name.lower(), f"hey {robot_name.lower()}", f"hi {robot_name.lower()}"]):
204+
response = f"Hello — I'm {robot_name}. How can I help you?"
205+
print(f"Robot (local): {response}")
206+
else:
207+
# Chat with OpenAI (guarded)
208+
try:
209+
if openai.api_key:
210+
system_prompt = f"You are a friendly robot companion named {robot_name}. Be concise and helpful."
211+
resp = openai.ChatCompletion.create(
212+
model="gpt-4o",
213+
messages=[
214+
{"role": "system", "content": system_prompt},
215+
{"role": "user", "content": user_input}
216+
]
217+
)
218+
# Access response safely
219+
response = resp.choices[0].message.content
220+
else:
221+
response = "(OpenAI API key not set) I can't call OpenAI, but I'm listening!"
222+
print(f"Robot: {response}")
223+
except Exception as e:
224+
print(f"OpenAI request failed: {e}")
225+
response = "Sorry, I couldn't think of a response."
226+
227+
# TTS response
228+
try:
229+
speak(response)
230+
except Exception as e:
231+
print(f"TTS failed: {e}")
232+
233+
# Determine emotion and send to Arduino
234+
emotion = get_emotion_from_response(response)
235+
send_command(f"EMO|{emotion}")
236+
237+
except KeyboardInterrupt:
238+
print('\nInterrupted by user')
239+
finally:
240+
# Cleanup
241+
print('Cleaning up...')
242+
if cap:
243+
try:
244+
cap.release()
245+
except Exception:
246+
pass
247+
if enable_preview:
248+
try:
249+
cv2.destroyAllWindows()
250+
except Exception:
251+
pass
252+
if ser:
253+
try:
254+
ser.close()
255+
except Exception:
256+
pass

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
OPENAI_API_KEY = "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"
2+
SERIAL_PORT = "COM6"
3+

diagnosticsapi

Whitespace-only changes.

0 commit comments

Comments
 (0)