בסדרה עתיקת היומין ״קרובים-קרובים״, הדודה המטורללת בגילומה של חנה מרון הנפלאה (שהחודש הזה, מאי 2024, נציין עשור לפטירתה), היתה מדברת עם העציצים. לפעמים יוצא לנו לדבר עם עציצים וגם חיות שלא תמיד יכולות לענות. אבל מה אם… העציצים יכלו לדבר?
ומה אם… אני יכול לגרום לעציץ שלי לדבר? בעבר – זה היה משגר אותי הישר אל בית מחסה ללוקים בשטיון, אבל היום, בעזרת הבינה המלאכותית וקסמי הרספברי פיי, אני יכול לאפשר לעציץ שלי לדבר. איך? בעזרת רספברי פיי, מודל LLM שמותקן על הרספברי, חיישן לחות וטמפרטורה ורמקול (בלוטות׳ או חוטי) וגם פייתון. נתחיל? בטח שנתחיל.
ידע מקדים
הפוסט הזה דורש הבנה והכרה מוקדמת עם רספברי פיי. לדעת להתחבר מרחוק לרספברי פיי עם SSH, לעבוד עם GPIO ורספברי פיי ולחבר חיישנים. אם אין לכם מושג – זה הפוסט שאפשר להתחיל איתו ובו אני מלמד מאפס איך עובדים עם רספברי פיי ומשם יש קישורים לפוסטים אחרים שלי בנושא שיתנו לכם את הידע הרלוונטי.
אני עושה שימוש בקוד פייתון אבל מתכנתים בכל שפה יוכלו להסתדר איתו (מקסימום עם עזרה מידידינו ChatGPT או קופיילוט).
תכנון
בגדול הבסיס הוא רספברי פיי שמקבל את המידע מחיישנים שנמצאים בעציץ, המידע הזה עובר לקוד פייתון, שמתרגם את המידע לבקשה ממודל LLM קל משקל בשם טייני דולפין שנמצא לוקלית על המכשיר. הפלט מהטייני דולפין עובר היישר אל espeak ומשם לרמקול שמחובר בבלוטות׳ ונמצא ליד העציץ. הנה, עשיתי דיאגרמה:
הכנת הסביבה
יש כמה הכנות מקדימות שכדאי לעשות.
התקנת ה-LLM
יש להתקין ollama על רספברי פיי יחד עם tinydolphin. גם פה אפשר לבקר בפוסט שלי שמסביר על התקנת בינה מלאכותית על רספברי פיי או להריץ את השורות האלו:
curl -fsSL https://ollama.com/install.sh | sh
ollama run tinydolphin
חיבור הרמקול
אפשר לחבר את הרמקול שלכם עם כבל 3.5 מ״מ או לחבר אותו בחיבור בלוטות׳ (לפוסט שלי המסביר על חיבור בלוטות׳ לרספברי פיי). אחרי ההתקנה, יש להתקין את espeak
sudo apt-get install espeak
ואבדוק שהקול עובד עם:
espeak "Hello"
הוא מדבר? יופי. אפשר להמשיך הלאה.
הכנת הקוד המקשר ובדיקה שהכל עובד
אחרי ההתקנות האלו, נבנה בינה מלאכותית שמדברת. ראשית, ניצור תיקיה בפרויקט, אפשר גם מרחוק עם VSCode (וכן, יש לי פוסט שמדבר על איך עובדים בנוחות עם קוד מרחוק על רספברי פיי). ניצור תיקית plant-project ובתוכה index.py.
לשם התחלה, ניצור קוד שקורא את הטמפרטורה במעבד, שולח פרומפט מתאים לטייני דולפין ואז שולח ל espeak. אני מעדיף לעשות את זה דרך שורת הפקודה אגב, ולא דרך מודולים של פייתון, כדי למנוע מכם לבזבז זמן על התקנות וגרסאות.
import os
import time
import subprocess
import json
def get_cpu_temperature():
"""Reads the CPU temperature from the system file."""
with open("/sys/class/thermal/thermal_zone0/temp", "r") as file:
temp = float(file.read()) / 1000
return temp
def run_command(command):
"""Executes a system command and returns the output."""
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.stdout.strip()
def main():
while True:
temp = get_cpu_temperature()
message = f"Write a 10 words message about the CPU Temperature: {temp} Celsius"
print("Run the message through the API")
print(message)
# Construct the curl command to send the POST request
curl_command = f"curl -s http://localhost:11434/api/generate -H 'Content-Type: application/json' -d '{{\"model\": \"tinydolphin\", \"prompt\": \"{message}\", \"stream\": false}}'"
print("Executing curl command...")
print(curl_command)
curl_output = run_command(curl_command)
print(f"Curl output: {curl_output}")
try:
# Parse the JSON output to get the response property
json_response = json.loads(curl_output)
ollama_output = json_response['response']
print(f"API response: {ollama_output}")
except json.JSONDecodeError:
ollama_output = "Failed to decode JSON response."
print(ollama_output)
except KeyError:
ollama_output = "The 'response' key is missing in the JSON response."
print(ollama_output)
# Speak the output of ollama run
os.system(f"espeak '{ollama_output}'")
time.sleep(120) # Sleep for 120 seconds
if __name__ == "__main__":
main()
אני יכול להסביר במילים על הקוד הזה אבל האמת היא שהוא מדבר בזכות עצמו. אני מבצע קריאה של הטמפרטורה, שולח פרומפט באמצעות ה-API ללוקלהוסט של ollama ואת הפלט שולח ל espeak. אם הכל יהיה תקין, אתם תשמעו את הרספברי פיי מדבר אליכם. שלב ראשון הושלם! אפשר כמובן לשחק עם הפרומפט כדי שיקרא לכם בשם הפרטי או יצעק/יחמיא בהתאם.
חיבור החיישנים
אבל אני רוצה שהעציץ ידבר, זה השלב של לחבר את החיישנים! בגדול יש לנו כמה אפשרויות לעשות את זה – הראשונה היא לחבר את החיישנים ישירות אל הרספברי פיי דרך ה-GPIO שלו. נצטייד בחיישן מים ונחבר אותו לרספברי פיי דרך ה-GPIO. הוא נותן לי פלט של 1 (יש מים!) או 0 (אין מים!). לחיישן לחות יש בדרך כלל 3 פינים. אחד שמסומן כפלוס שהולך לפין שמוציא מתח של 3.3V, אחד כמינוס, שהולך לפין שמוציא גראונד (בד״כ פין 39) והשלישי הולך ל GPIO לפי בחירתכם כשאני ממליץ על 17. אם יש לכם 4 פינים – אז התעלמו בשלב זה מהפין המסומן כ AO או כ ADD ותיקחו את הפין DD או DO ותחברו אותו ל GPIO17.
כאן יש דוגמה: הכבל האדום מחובר לפין של המתח, החום לפין של הגראונד והאדום ל GPIO17.
השלב הבא הוא לנסות ולראות אם החיישן עובד. הריצו את הקוד הזה ונסו להוציא או להכניס את החיישן לסביבה רטובה.
import RPi.GPIO as GPIO
import time
# Setup
sensor_pin = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor_pin, GPIO.IN)
try:
while True:
if GPIO.input(sensor_pin):
print("Water detected!")
else:
print("No water detected.")
time.sleep(1) # Read every second
finally:
GPIO.cleanup() # Clean up the GPIO to reset the mode
עכשיו נחבר את הכל ונשתמש בפלט כבסיס לטיינידולפין. הנה התוצאה:
import os
import time
import subprocess
import RPi.GPIO as GPIO
import json
# Setup GPIO
sensor_pin = 17 # Change as per your connection
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor_pin, GPIO.IN)
def run_command(command):
"""Executes a system command and returns the output."""
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.stdout.strip()
def main():
try:
while True:
if GPIO.input(sensor_pin):
message = "You are a happy plant named moshe tell me about it. In 10 words"
else:
message = "You are thirsty plant named moshe complain about it. In 10 words."
print("Run the message through the API")
print(message)
# Construct the curl command to send the POST request
curl_command = f"curl -s http://localhost:11434/api/generate -H 'Content-Type: application/json' -d '{{\"model\": \"tinydolphin\", \"prompt\": \"{message}\", \"stream\": false}}'"
print("Executing curl command...")
print(curl_command)
curl_output = run_command(curl_command)
print(f"Curl output: {curl_output}")
try:
# Parse the JSON output to get the response property
json_response = json.loads(curl_output)
api_output = json_response['response']
print(f"API response: {api_output}")
except json.JSONDecodeError:
api_output = "Failed to decode JSON response."
print(api_output)
except KeyError:
api_output = "The 'response' key is missing in the JSON response."
print(api_output)
# Speak the output of the API
os.system(f"espeak \"{api_output}\"")
time.sleep(120) # Check every two minutes
finally:
GPIO.cleanup() # Clean up the GPIO to reset the mode
if __name__ == "__main__":
main()
ו…
הוא עצוב אם אין לו מים ושמח אם כן וגם יודע לתקשר את זה ועושה את זה כל שתי דקות.
או קיי, אבל למה כל שתי דקות? האם אפשר לחבר לו חיישן קרבה? האם אפשר לגרום לו לזהות פרצופים? התשובה היא כמובן – כן ו…כן! אבל פרה פרה.
הבעיה המרכזית היא שחלק משמעותי מהחיישנים המעניינים הם אנלוגיים ורספברי פיי, מעשה שטן, עובד רק עם חיישנים אנלוגיים. יש שני פתרונות לזה. אחד הוא להשתמש בממיר שימיר פלט אנלוגי לדיגיטלי (זה כאב ראש) והשני הוא לחבר esp32 או מיקרו בקר לחיישן ולתת לו לשלוח את המידע לרספברי פיי דרך הרשת האלחוטית. אבל את זה נעשה (אולי) בפוסט הבא.
6 תגובות
פוסט מגניב!
באיזה דגם של RPI אתה משתמש?
האם RPI ZERO מספיק חזק בשביל להריץ את מודל השפה?
תודה רבה! אין כרגע מצב ש Zero יוכל להריץ את זה. בינתיים לפחות.
פוסט מגניב!
באיזה RPI אתה משתמש?
האם RPI ZERם יהיה מספיק חזק בשביל להריץ את מודל השפה?
זה מדהים!
האם אפשר לקחת מודל ולהוסיף לאימון שלו שהוא יכיר את בני המשפחה, או ילמד טקסטים מסוימים שחשובים לי?
האמת, שזה מדהים!
אני השתמשתי במכשיר כענן ביתי שלכל אחד מבני הבית, יש משתמש משלו לענן, וכמובן תיקייה משותפת לכולם..
אם אספר לחמותי שאני יכול לגרום לעציצים שלה לדבר.. היא תעוף על זה!)
בלוג מעולה, תודה
מדריך מעולה.
משהו אחד שנתקלתי בו – לפחות אצלי, העברת הטקסט המוכן לפוקנציית הדיבור נכשלה כל פעם, עד שהבנתי שברגע שיש בטקסט גרש בודד הוא סוגר את הפקודה ויוצר שגיאה (סוג של הזרקת SQL לא מכוונת). הפתרון שלי היה לעשות החלפה של הגרשיים בתו אחר וזה פתר את הבעיה.