לגרום לעציץ שלכם לדבר

כך תשתמשו ברספברי פיי, חיישנים וגם בינה מלאכותית שמותקנת על הרספברי פיי (כן) כדי ליצור... עציץ המדבר.
תמונה של עציץ, רספברי פיי ורמקול

בסדרה עתיקת היומין ״קרובים-קרובים״, הדודה המטורללת בגילומה של חנה מרון הנפלאה (שהחודש הזה, מאי 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.

כאן יש דוגמה: הכבל האדום מחובר לפין של המתח, החום לפין של הגראונד והאדום ל 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 או מיקרו בקר לחיישן ולתת לו לשלוח את המידע לרספברי פיי דרך הרשת האלחוטית. אבל את זה נעשה (אולי) בפוסט הבא.

פוסטים נוספים שכדאי לקרוא

צילום מסך של סוואגר
יסודות בתכנות

openAPI

שימוש בתשתית הפופולרית למיפוי ותיעוד של API וגם הסבר בסיסי על מה זה API

בינה מלאכותית

Safeguards על מודל שפה גדול (LLM)

פוסט בשילוב עם פודקאסט וסרטון על ההגנות שאפשר להציב על LLM בסביבת פרודקשן

גלילה לראש העמוד