לולאות for וthread's
-
שלום!
אני כותב קוד פייתון על הרספברי ובו לולאה הקוראת שידורי RS232.
אני מחפש דרך לגרום לכך שאם אלחץ על הכפתור הלולאה תעצר.
הבעיה שלי היא איך אני יכול לגרום לכך שהסקריפט יהיה כל הזמן בכוננות לקבל את הלחיצה שתעצור אותו.
הקוד שלי הוא:import serial from gpiozero import Button from time import sleep a = 0 aa = 0 button = Button(27) try: ser = serial.Serial('/dev/ttyUSB0') ser.port = '/dev/ttyUSB0' baudrate=9600 timeout=1 xonxoff=False rtscts=False write_timeout=None dsrdtr=False inter_byte_timeout=None exclusive=None ser.close() ser.open() while 1: for cc in ser.read(): if button.is_pressed: ser.close() print("Pressed") else: a =int(cc) print(a) aa = aa + a print ('סכום מצטבר של הערך' , aa) except OSError: print ('lo nimtza')
תודה רבה!!
-
@aaron ! זה לא פותר.
בגלל יש פה משהו מוזר מאוד. (בנושא כללי של לולאות.
לולאה אמורה לרוץ כל הזמן. נכון?
אבל פה זה נראה שאם יש בייטים שנשלחים מהסריאלי אז הלולאה רצה ויש הדפסה אבל כל שאר הזמן הלולאה מתה.
וזה עושה שאי אפשר לומר שאם לחצת על כפתור התהליך ייעצר .
כי רק אם אלחץ, ותוך כדי הלחיצה אני ישלח עוד בייטים אז הלולאה תגיע לפקודה "button.is_pressed" ותעצור.
מה עושים??? -
@יוסף1111 אמר בהתנהגות של לולאת FOR:
לולאה אמורה לרוץ כל הזמן. נכון?
לא בדיוק, קוד בפייתון (ובעצם בכל שפה) רץ כסדר, כלומר מבצע את הפקודות שורה שורה בסדר שנכתבו. יש לפעמים פקודות שאומרים לפייתון ללכת לישון לזמן מה, ואז הפייתון הולך לישון כמו ילד טוב לזמן המוגדר ואז הוא מתעורר וממשיך לפקודה הבאה.
לולאה רצה בדיוק כמו קוד רגיל, רק כשהוא מגיע לסוף הוא מתחיל שוב מהתחלה.
בוא ננתח מה קורה.
אני מדלג על החלק הראשון שלא כל כך מעניין,
בשורה 20 אתה כותבwhile 1:
, הכוונה היא: "א. תבדוק אם התנאי (1
) הוא חיובי (התנאי1
תמיד יהיה חיובי), ב. במידה וכן, תריץ את הבלוק הבא, וכשתגיע לסוף תחזור שוב לשלב א."
בשפת בנ"א אתה מריץ את הבלוק בלולאה אינסופית.
בשורה הבאה אתה כותב:for cc in ser.read():
, הכוונה היא: אתה מצהיר שהערך שמוחזר מ-ser.read()
הוא iterable, הכוונה שהיא סוג אובייקט שיכולה לספק לך ערכים, ואפשר לשלוף את הערכים אחד אחד.
אתה מפקד על פייתון לקחת את ה-iterable הזה ולשלוף את הערכים אחד אחד, בכל שליפה של ערך הוא אמור לשים את הערך במשתנהcc
ולהריץ את הבלוק.
איך פייתון שולפת את הערכים מהאובייקט הזה?
היא קוראת את הפונקציה__iter__
כדי לקבל אובייקט מסוג iterator, על ה-iterator הזה אפשר לקרוא פונקציית__next__
שבכל קריאה מחזירה את הערך הבאה.מאיפה הערכים האלו שהאובייקט מחזירה?
הם מגיעים מהסריאל פורט.
ערכים אלו לא תמיד זמינים מיד, לפעמים צריך לחכות עד שהם יגיעו מאן שהם אמורים להגיע. ומה קורה אם הערכים עוד לא זמינים?
הפונקציה ששולפת את הערכים פשוט הולכת לישון כי אין לה מה לעשות עד שהערכים יהיו זמינים, לפני שהיא ישנה היא מבקשת ממערכת ההפעלה להעיר אותה ברגע שמשהו זמין מהסיריאל פורט. ברגע שקלט מגיע, מערכת ההפעלה מעוררת את הפונקציה מהשינה העריבה והפונקציה מקבלת את הדאטה ומחזירה אותה לקורא של הפונקציה. בזמן הזה שהיא ישנה, התוכנה שלך מושבתת.
בשורה 22 (שרצה אחרי היקיצה מהשינה) אתה בודק אם הלחצן מולחץ, במידה וכן אתה מריץ (שורה 23)ser.close()
. הפעולה הזו גורמת שבאיטרציה הבאה של הלולאה כאשר פייתון תנסה לשלוף את הערך הבא עבור הלולאה, היא תקבל תשובה "מצטער, נגמר". זה מפסיק את הלולאה, וגורם ללולאת ה-for
להסתיים, הקוד ממשיך אחרי הלולאה. מכיון שלולאת ה-for
מקוננת בתוך לולאתwhile
הקוד תחזור שוב ללולאה החיצונית שזה יריץ שוב שורה 21. (האם התכוונת לזה? זה ייכשל כי לכאורה אי אפשר לקרואser.read()
אחרי הסגירה של הפורט...) -
@יוסף1111 עכשיו שהבעיה נהירה לך, אפשר לחשוב על פתרון,
מעיון קצת בתיעוד של ספריית pySerial אני רואה שתי פתרונות אפשריות.
א. אפשר להגדיר timeout לפעולת הקריאה מהפורט, כלומר, פעולת הקריאה מבקשת ממערכת ההפעלה: "תעיר אותי אם יש דאטה, או אחרי XX שניות" כך אתה יכול לוודא שהלולאה לא תהא מושבתת לזמן ארוך מדי.
בעצם, עכשיו אני שם לב שהגדרת timeout, רק שלא עשית את זה נכון.
מה שכתבת בשורה 11:timeout=1
מתורגם כיצירת משתנה חדשה בשםtimeout
שהערך שלה 1, זה לא מגדיר את ה-timeout של הפורט. צריך לכתוב כך:while not button.is_pressed: ser = serial.Serial(port='/dev/ttyUSB0', timeout=1) for cc in ser.read(): a =int(cc) print(a) aa = aa + a print ('סכום מצטבר של הערך' , aa)
עכשיו בכל איטרציה של הלולאה החיצונית אתה פותח את הסריאל פורט עם timeout, אם יש דאטה בתוך הזמן אז הבלוק הבא מבוצע, אם לא אז אתה חוזר ללולאה החיצונית שבודקת אם הלחצן לחוץ, במידה ולא היא פותחת שוב את הפורט וחוזר חלילה.
-
ב. פתרון נוסף:
אני רואה בתיעוד של pyserial שאפשר לבטל נסיון קריאה מ-thread אחר, ואם כן אפשר ככה:
(ייתכן שיש טעויות בקוד, אבל זה הרעיון)import threading import serial from time import sleep from gpiozero import Button button = Button(27) ser = None def run(): while ser is None or not button.is_pressed: sleep(0.5) ser.cancel_read() t = threading.Thread(target=run) t.start() ser = serial.Serial('/dev/ttyUSB0') for cc in ser.read(): a = int(cc) print(a) aa = aa + a print('סכום מצטבר של הערך', aa)
-
@yossiz זה אכן מה שעזר!
היש סיבה לכך שזה מתאים רק לכפתור פיזי ולא לקריאת פונקציה.
היינו:- עם ספריית pynput יצרתי פונקציה שתשנה ערך של משתנה אם מקש כלשהו נלחץ. [ואז סוגר את הפורט]
- כיוון שקראתי לפונקציה לפני קריאת הבייטים (שורה 60) הקוד עוצר שם. האם אי אפשר שירוצו "במקביל"? (קראתי על thread, זהו הפיתרון? קראתי גם שהוא לא פיתרון יציב).
- הקוד: פונקציית מקשים. קריאה לפונקציה. לולאת פור לסריאל.
עצירת הלולאה אם המשתנה השתנה
תודה!
import serial stc(func = stop_ser) while True: for i in ser.read(): sa =int(i) saa = saa + sa print (saa') n99 = str(saa) if stop_ser != 0: break seri() print('!!!')
-
@יוסף1111 הקוד הזה ממש מאתגר אותי... קשה לי להבין מה קורה שם. האם יש מצב שתוכל לכתוב הקוד הכי קצר שמתמצת את השאלה שלך?
אגב, כדאי שתתרגל לתת שמות תיאוריים (כלומר שהשם מתאר את הייעוד של המשתנה) למשתנים. (אפילו שם כמו
narishkayt
יותר טוב משם בעל אות אחת, או בעל כמה אותיות בלי משמעות ברורה) -
אני רואה לצערי שthread היא לא דרך מוצלחת כל כך. זה עושה כל כמה פעמים runtime error!. יש דרך אחרת לפקח על 2 דברים "ביחד"?
אני מסביר: אני צריך מחד לקבל נתונים מיציאת COM ומאידך לעצור את התהליך על ידי הקשה על מקש.
אי אפשר כמובן להשתמש באינפוט כי כל התהליך יעצור, אלא אני משתמש ב pynput שהיא ספריה להשגיח על מקשי המקלדת.
וכדי שמאידך הנתונים לא יאבדו בזמן שהמעבד עסוק עם pynput השתמשתי עם thraed. שהוא נכשל כאמור לפעמים.
תודה! -
@יוסף1111 מה הפונקציה שבודקת את הלחיצה?
אתה אמור לעבוד ב loop אינסופי, לקרוא מהסריאל, עם timeout, ואז לבדוק את מצב הלחיצה.
וחוזר חלילה.
במידה ונלחץ, אתה יוצא מה loop בדרך רגילה
מה שתעשה זו לולאת while שהתנאי שלה זה שלא נלחץ כפתור.
זו הדרך לדעתי שעושים במערכות embedded. אני לא חושב שיש דרך אחרת.אתה צריך להבין שמחשב מעצם טיבו וטבעו לא עושה אף פעם 2 פעולות במקביל. הוא לא יכול באמת גם לקרוא מהסריאל וגם לבדוק את הלחיצה.
אלא הוא עובר בין המשימות שיש, כל הזמן. (אני לא מדבר על ריבוי מעבדים).
וגם שיש לך 2 threads זה לא באמת רץ במקביל, אלא כשתי "תוכנות". שהמעבד כל פעם עובר בין אחת לשניה ועושה מה שהיא מבקשת ממנו (פעם שמעתי תיאור ציורי שהמעבד הוא כמו משרת שצריך לשרת הרבה נסיכים). הוא רץ ביניהם ועושה כל פעם משימה אחת בלבד. ובגלל שזה מאוד מהר (מיליוני עד מילארדי פעמים בשניה של פעולות גרעיניות- אטומיות, כמדומני) זה נראה לנו במקביל -
@מנצפך , אתה מדבר על ספריית pynput או באופן כללי?
כי ממה שניסיתי אכן יש ספריות שפועלות כמו שאתה כותב שאין צורך לעשות טיים אאוט, אבל פונקציות ספריה זו (שהיא היחידה שעובדת על לינוקס) ממתינות עד לקבלת קלט /רטורן.
השגיאה שהיתה בטריד נפתרה.(הוגדר בתוך הפונקציה. הruntime error היה כי ניסה לפתוח פעם נוספת את אותו הטריד)
אבל מעדיף להסתדר בלעדיו כי אומרים שזה לא דבר יציב.