נמשיך עם רספברי פיי והאינטרנט של הדברים ונבנה ממשק משתמש על הרספברי פיי ששולט ברכיבים ומקבל מידע מחיישנים שונים. הפעם נשתמש ב flask שהיא חבילה של פייתון ונבדוק את היכולות שלה.
הפרויקט דורש ידע בסיסי בפייתון html ו javascript.
התקנת flask
flask היא ספריה של פייתון שנותנת לנו לבנות אפליקציות אינטרנט פשוטות עם כמה שורות של קוד. זה חוסך לנו הורדת שרת אפאצ’י ושפת PHP אל הרספברי פיי , ואנחנו משתמשים בפייתון שגם ככה נמצאית עליו.
קודם כל נתחבר אל הרספברי פיי ב SSH ונכניס את הפקודה לטרמינל:
sudo apt-get install python-flask
עכשיו ניצור את תיקיית השורש של האפליקציה שנבנה בתוך תיקיית pi:
mkdir app
בתוך התיקייה ניצור את הקובץ הראשי של האפליקציה:
nano app.py
ונכניס את הקוד הבא:
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run(host='0.0.0.0',port=80,debug=True)
השורה האחרונה בקובץ אומרת לflask לשרת בפורט 80 במקום בפורט ברירת המחדל שלו שהוא 5000.
0.0.0.0 אומר שאפשר להכנס לאפליקציה מכל מחשב ולא רק מהרספברי פיי.
שימו לב להזחה בפייתון – אחרי נקודותיים השורה הבאה תמיד מתחילה עם הזחה של 4 רווחים בגלל שאין בפייתון סוגריים כמו בשפות אחרות המפרש יזרוק error אם לא תהיה הזחה.
בקובץ אנחנו אומרים לflask לשרת בתיקיית השורש את הפונקציה hello כלומר שאם נכניס עכשיו את הכתובת של הרספברי פיי לדפדפן נראה את השורה hello world כי זה מה שמוחזר לדפדפן. צריך כמובן להפעיל את האפליקציה לפני שעושים את זה:
sudo python app.py
בניית דף HTML
זהו עכשיו יש לנו שרת שמציג מידע לכל לקוח שנכנס. כדי לשכלל את זה קצת אנחנו רוצים להציג דף HTML מלא וכדי לעשות זאת נמחק את פונקציית hello ונכניס במקומה את הפונקציה הבאה מתחת ל route:
def index(): return render_template('index.html')
עכשיו flask יחפש את הקובץ הזה בתיקיית templates שאותה צריך ליצור בתיקיית האפליקציה. ניצור גם תיקיית static שבה flask מחפש קבצי css javascript ותמונות:
mkdir templates mkdir static
את כל יצירת התיקיות והקבצים ועריכתם אפשר לעשות על ידי לקוח ftp שנקרא filezilla שאם עוד אין לכם אותו אז כדאי להוריד כי שימוש בו יחסוך לכם הרבה זמן.
בתיקיית templates ניצור קובץ HTML פשוט ונקרא לו index.html :
<!doctype html> <html lang="he_IL"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body{ direction:rtl; color:maroon; } </style> </head> <body> <!-- Add your site or application content here --> <h1>רספברי פיי ממשק גרפי</h1> <h3>חיווי: <span id='led_log'></span></h3> <button id="led">כיבוי והדלקת נורה</button> <script src="https://code.jquery.com/jquery-1.12.0.min.js"></script> <script type=text/javascript> $('#led').on('click',function(){ $.post('/led',function(data){ $('#led_log').html(data) }) }) </script> </body> </html>
שליטה ברכיבים דרך האינטרנט
הקוד בתחתית הקובץ הוא javascript והוא מקשר אל הכפתור שיצרנו (button) בקשה של הדלקה וכיבוי נורה ואנחנו שולחים בקשת post אל האפליקציה בכל פעם שהכפתור נלחץ . בקשת הפוסט הולכת ל route /led שתכף ניצור אותו באפליקצית הפייתון. הבקשה הולכת ישירות בלי צורך לרענן את הדף בטכנולוגיה שנקראית ajax שקוראת לשרת בלי לרענן את הדף. לאחר שהבקשה מסתיימת המידע המוחזר מוחדר אל תוך האלמנט שאנחנו בוחרים בתוך דף ה HTML.
עכשיו ניצור את הנתיב led שאליו הולכת הבקשה בקובץ הפייתון והוא נמצא מתחת לנתיב השורש. נעשה גם את כל ההכנות לשימוש ב GPIO:
import RPi.GPIO as GPIO GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(23, GPIO.OUT) led_state = False from flask import * app = Flask(__name__) @app.route("/") def index(): return render_template('index.html') @app.route("/led",methods=['POST']) def led(): global led_state led_state = not led_state GPIO.output(23, led_state) return str(led_state) if __name__ == "__main__": app.run(host='0.0.0.0',port=80,debug=True)
עכשיו כל פעם שלוחצים על הכפתור נשלחת בקשה ברקע ופין מספר 23 ברספברי פיי משנה את המצב שלו. הקוד גם מחזיר תגובה ללקוח בקשר למצב הפין ואת התגובה אנחנו מחדירים אל דף הHTML לתוך האלמנט led_log.
כך יראה הדף בכל פעם שנכניס את כתובת הרספברי פיי אל הדפדפן.
עכשיו נקבל גם חיווי מכפתור לחיצה ישירות אל ממשק המשתמש. הנתיב בקובץ הפייתון מזכיר את הנתיב שבנינו לנורת הלד רק שהפעם אנחנו נותנים לפין 24 לחכות ללחיצה:
@app.route("/button",methods=['POST']) def button(): if GPIO.input(24): global button_state button_state = not button_state return str(button_state) else: return str(button_state)
הקוד של jquery הוא קצת שונה מקודם כי הוא לא מופעל על ידי לחיצה אלא על ידי תזמון כל חצי שניה . ב javascript ישנה פונקציה שנקראת setinterval שגורמת לפונקציה לחזור על עצמה כל זמן קבוע שאנחנו בוחרים:
setInterval(function(){ $.post('/button',function(data){ $('#button_log').html(data) }) }, 500);
עכשיו יש לנו נורה שאפשר להדליק ולכבות וכפתור לחיצה שנותן חיווי אל הממשק.
בניית ממשק המשתמש על הרספברי פיי
אפשר בצורה הזו ליצור הרבה כפתורים שנותנים פקודות לרספברי פיי אבל החלטתי ללכת על תמונות במקום כפתורים. כל התמונות ב flask צריכות לשבת בתוך תיקיית static שיצרנו קודם והדרך לגשת אליהם היא כזו:
<img src="{{ url_for('static', filename='img.jpg') }}">
אפשר לשים איזה תמונות שרוצים עדיף שיהיו באותו גודל ואותו יחס.
על חלק מהתמונות יהיה אפשר ללחוץ כמו שלוחצים על כפתור, וחלק מהתמונות יהיו רק לתצוגה של נתונים שמגיעים בזמן אמת לרספברי פיי.
הוספת חיישנים אנלוגים עם ממיר אנלוגי/דיגיטלי
כדי לקבל אל הממשק הגרפי נתונים מחיישנים אנלוגיים נשתמש בממיר אנלוגי/דיגיטלי ADC כדי שהרספברי פיי יוכל לקלוט אותם, כי כידוע אין לו פינים לכניסות אנלוגיות . נשתמש בממיר הבא שעובד על 8 ביט (ערכים 0-255) ומה שנחמד בממיר הזה הוא שיש עליו כבר 3 חיישנים שאפשר לקלוט – חיישן אור(LDR), חיישן טמפרטורה(thermistor), ופוטנציומטר. הוא מתקשר עם הרספברי פיי בפרוטוקול I2C ויש לו 4 כניסות אנלוגיות ויציאה אנלוגית אחת.
כל חמש שניות נוציא בקשת post ל flask ואז במכה אחת נוציא את שלושת הערכים ונשלח אותם חזרה לדפדפן במבנה נתונים שנקרא json.
קוד ה jquery ששולח את הבקשה מקבל חזרה את התגובה עם הערכים ומחדיר אותם לתוך הדף בזמן אמת בלי צורך לרענן את הדף.
אחרי שסיימנו עם שלושת החיישנים האלו נוסיף גם ממסר שאפשר להדליק ולכבות אותו על ידי לחיצה על התמונה שלו ואחרי כל לחיצה החיווי שלו מראה true או false .
הקוד המלא של הפרויקט
<!doctype html> <html lang="he_IL"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body{ direction:rtl; color:maroon; } p{ float:right; border:1px solid; border-radius:4px; } </style> </head> <body> <!-- Add your site or application content here --> <h1>רספברי פיי ממשק גרפי</h1> <div id='pictures'> <p><img id='led' src="{{ url_for('static', filename='led.jpg') }}"><span id='led_log'></span></p> <p><img id='ldr' src="{{ url_for('static', filename='ldr.jpg') }}"><span id='ldr_log'></span></p> <p><img id='servo' src="{{ url_for('static', filename='servo.jpg') }}"><span id='servo_log'></span></p> <p><img id='potentiometer' src="{{ url_for('static', filename='potentiometer.jpg') }}"><span id='potentiometer_log'></span></p> <p><img id='pir' src="{{ url_for('static', filename='pir.jpg') }}"><span id='pir_log'></span></p> <p><img id='button' src="{{ url_for('static', filename='button.jpg') }}"><span id='button_log'></span></p> <p><img id='relay' src="{{ url_for('static', filename='relay.jpg') }}"><span id='relay_log'></span></p> <p><img id='temp' src="{{ url_for('static', filename='temp.jpg') }}"><span id='temp_log'></span></p> </div> <script src="https://code.jquery.com/jquery-1.12.0.min.js"></script> <script type=text/javascript> $('#led').on('click',function(){ $.post('/led',function(data){ $('#led_log').html(data) }) }) setInterval(function(){ $.post('/button',function(data){ $('#button_log').html(data) }) }, 500); setInterval(function(){ $.post('/sensors',function(data){ $('#ldr_log').html(data.ldr) $('#temp_log').html(data.temp) $('#potentiometer_log').html(data.potentiometer) }) }, 5000); $('#relay').on('click',function(){ $.post('/relay',function(data){ $('#relay_log').html(data) }) }) </script> </body> </html>
וזה הקובץ המלא של פייתון app.py:
import RPi.GPIO as GPIO import time from smbus import SMBus bus = SMBus(1) GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(23, GPIO.OUT) GPIO.setup(18, GPIO.OUT) GPIO.setup(24, GPIO.IN,pull_up_down=GPIO.PUD_DOWN) led_state = False button_state = False relay_state = False from flask import * app = Flask(__name__) @app.route("/") def index(): return render_template('index.html') @app.route("/led",methods=['POST']) def led(): global led_state led_state = not led_state GPIO.output(23, led_state) return str(led_state) @app.route("/button",methods=['POST']) def button(): if GPIO.input(24): global button_state button_state = not button_state return str(button_state) else: return str(button_state) @app.route("/sensors",methods=['POST']) def sensors(): bus.write_byte(0x48, 0) bus.read_byte(0x48) ldr = bus.read_byte(0x48) bus.write_byte(0x48, 2) bus.read_byte(0x48) temp = bus.read_byte(0x48) bus.write_byte(0x48, 3) bus.read_byte(0x48) potentiometer = bus.read_byte(0x48) return jsonify({"ldr": ldr, "temp":temp, "potentiometer":potentiometer}) @app.route("/relay",methods=['POST']) def relay(): global relay_state relay_state = not relay_state GPIO.output(18, relay_state) return str(relay_state) if __name__ == "__main__": app.run(host='0.0.0.0',port=80,debug=True)
סיכום flask
או קיי אז מה אפשר להבין מהממשק ומה הערכים האנלוגיים האלה אומרים? אפשר להבין שהנורה דולקת , שחיישן האור מראה 189 מתוך 255 מה שאומר שהאור דולק, הפוטנציומטר נמצא בערך בחצי סיבוב, הכפתור במצב כבוי וכך גם הממסר. מד הטמפרטורה מראה 130 שאת זה צריך לכייל לטמפרטורת צלזיוס אבל בגדול לא הייתי מסתמך על הטרמיסטור שנמצא על המודול.
את הסרוו וחיישן האינפרה לא היה לי כח לסיים אבל העקרון נשאר אותו דבר כמו שאר הרכיבים. בצורה הזו אפשר להפעיל כל רכיב דרך האינטרנט וגם לקבל מידע בזמן אמת מחיישנים.
flask היא פלטפורמה קלה מאוד לשימוש אחרי שמבינים אותה ולאפליקציות קטנות עדיף להשתמש בה במקום להוריד apache ו php .
חבל שהמורה שלי לפייתון וJS לא היה כזה בהיר!! קצר וקולע.