הרמזור - חיווי מצב ההזמנות

עובדים על פרוייקט? ספרו לכולם על תהליך העבודה. תנו לנו ללמוד מכם!
שלח תגובה
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

שלום לכם.

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

בדרך כלל אני מתכנן את כל מה שאני בונה בראש ולא מתעד את התהליך התכנון, אבל הפעם אתעד כאן את תהליך הבניה, השיקולים לבחירת הרכיבים, הכיוון המחשבתי וכו'.

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

על מה חשבתי בינתיים?
ראיתי באתר של Adafruit את הרמזור הזה:
תמונה
https://www.adafruit.com/product/2993

הרמזור נראה מגניב וכנראה מתאים לחיווי. הייתי רוצה שיהיו יותר צבעים (עוד מעט אפרט), אז הלכתי לחפש מוצרים דומים ב-AliExpress ומצאתי מוצרים דומים גם עם 5 צבעים, אבל המחירים לא ממש ידידותיים... וגם במקרה שמתי לב שהמנורות של המוצר הספציפי שחשבתי להזמין לא דולקות קבוע, אלא מהבהבות, מה שלא מסתדר עם התוכניות שהיו לי.
אלה מוצרים תעשייתיים ומסתבר שיש כמה דגמים עם מתח הפעלה שונה. דגם עם המתח המינימלי שמצאתי הוא 12V שזה לא כזה נורא, אבל בסוף ויתרתי על הכיוון הזה. גם ההיבהובים לא מתאימים לי וגם כדי לספק 12V צריך ספק (שתופס לי שקע ומקום) וצריך להשתמש בממסרים או MOSFETs כדי למתג את המתח. תוך כדי בישול הרעיון בראש חשבתי על פתרון פשוט יותר לתפעול.

אילו חיווים צריך?
אז ככה... אני צריך חיווי פחות או יותר לכל סוג של צורת משלוח. אפשר לאחד כמה מהם יחד ותכף אעשה רשימה.
חוץ מסוגי המשלוחים יש גם מצבים שונים של הזמנות שהייתי רוצה לדעת עליהם. בזמנו פירטתי את המצבים כאן.
במערכת שלי להזמנות יש כמה מצבים. כשההזמנה נקלטת היא נמצאת במצב "ממתין לבדיקה", צריך לאשר את ההזמנה ואז היא יכולה לעבור למצב "שמור במלאי" אם יש את כל המוצרים במלאי. אחרי זה אני מדפיס את ההזמנה ומעביר אותה למצב "בהכנה" וכשהכל ארוז ומוכן למשלוח הוא עוברת למצב "נארז".
המצבים "ממתין לבדיקה", "שמור במלאי" ו-"בהכנה" הם המעניינים ביותר למקרה הזה ואני צריך חיווי שונה לכל אחד מהם.
אם אני לבד במחסן ונכנסת הזמנה, אז אני צריך לדעת שיש הזמנה דחופה במצב "ממתין לאישור". אם כבר אישרתי את ההזמנה ויש מי שעוזר לי במחסן, אז הוא צריך לדעת שיש הזמנות במצב "שמור במלאי" כדי להדפיס אותן ולהכין למשלוח. גם מהמצב "בהכנה" לא הייתי רוצה להתעלם כי כבר קרו מקרים שלא לחצתי טוב על כפתור ההדפסה והעברתי את ההזמנה ל-"בהכנה", כך שההזמנות יכולות להיתקע במצב הזה.

בינתיים החלטתי שהזמנות במצב "ממתין לאישור" יגרמו לחיווי להבהב בקצב איטי כלשהו, נניח כל שניה, והזמנות שאושרו ועברו למצב "שמור במלאי" ידליקו את החיווי באופן קבוע.
את הצבעים אפשר לחלק כך:
  • הזמנות דחופות (משלוח מעכשיו לעכשיו ואיסוף עצמי) יקבלו חיווי אדום.
  • הזמנות שנאספות ע"י השליחים (משלוחי FedEx ו-BoxIt) יקבלו חיווי צהוב.
  • הזמנות שנשלחות בדואר (דואר רשום ומעטפות 24), שהן פחות דחופות בדרך כלל יקבלו חיווי ירוק.
  • הזמנות במצב "בהכנה" יקבלו חיווי כחול.
  • הצעות מחיר והזמנות מיוחדות (ייצור מעגלים וכו') יקבלו חיווי בצבע לבן.
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

המשך...

בנושא החומרה
פסלתי את הכיוון של רמזור המוכן בגלל שאני חושב שאפשר להסתפק ברכיבים שיש לי באתר כדי לבנות משהו שיעשה את העבודה, אולי יראה פחות יפה, אבל זה פחות חשוב לי.

צריך 5 צבעים, אז אפשר להשתמש בפרופיל אלומיניום הזה כגוף הרמזור:
תמונה

קוטר החורים הגדולים של הפרופיל הוא 1/2" (12.7 מ"מ). אפשר לנסות להכניס לתוכם את הגומיות האלה:
תמונה
קוטר של החור בגומיה הוא כ-9.5 מ"מ, אנסה לדחוף לתוכו את הלד בקוטר 10 מ"מ. אם לא אצליח, אנסה להרחיב קצת את החור, או שאם גם זה לא יצליח, אפשר יהיה להדביק את הלדים עם דבק חם ישירות לפרופיל האלומיניום.
את הלדים אפשר יהיה להרכיב מ-3 הצדדים של הפרופיל כדי לראות את החיווי מכל הצדדים. הפרופיל יהיה צמוד לקיר, כך שלא צריך את הצד הרביעי, אבל גם אם הייתי צריך לראות את החיווי מכל הצדדים, אז אפשר לסגור אותו עם הפנלים האלה ולהשלים את הצד הרביעי בצורה דומה לשלושת האחרים:
תמונהתמונהתמונה

יש 2 סוגי לדים בקוטר 10 מ"מ: עם ראש צבוע ואטום וחזקים מאוד עם ראש שקוף:
תמונהתמונהתמונה
אנסה בהתחלה את הצבועים כדי לוודא שרואים אותם כמו שצריך, ואם לא, אז אפשר ללכת על החזקים. אני פשוט חושש שזה יציק יותר מדי בעיניים.

כדי שכל הסיפור יראה כמו רמזור, אפשר להשתמש צינור אלומיניום מתחת לפרופיל כרגל של הרמזור ולחבר אותו לפלטה עגולה שתשמש כמעמד:
תמונהתמונה


בנושא האלקטרוניקה
חשבתי להשתמש בכרטיס A-Startלבקרה על הכל. הוא קטן, זול, יש לו חיבור USB שישמש במקור למתח ודי הרבה קווי IO שיספיקו גם לשכלולים עתידיים אם יהיה צורך.
תמונה

רשמתי קודם שלא רציתי להתעסק עם ממסרים או MOSFETs כדי לשלוט על הלדים והיה לי כיוון אחר... ובכן, גם כדי לשלוט על 15 לדים אני צריך להוסיף רכיבים כי מיקרובקר לא יכול לספק את הזרם, במיוחד אם ארצה להפעיל את הלדים החזקים. הכיוון שלי הוא להשתמש בשבב MAX7219 שזה התפקיד שלו בחיים, להפעיל עד 64 לדים בצורה יעילה. הוא אומנם יכול לספק רק עד 40mA ללד, מה שיכול להיות שלא יספיק כדי להפעיל את הלדים החזקים בעוצמה המלאה (כ-100mA), אבל יש לי תחושה שגם 40mA יאירו אותם כמו שצריך (במקרה ולדים הצבעוניים לא יראו מספיק חזק). במקרה הכי גרוע אצטרך לוותר על ה-MAX7219 ואפעיל את הלדים דרך MOSFETs לכל צבע לד, אותם אפעיל ישירות מהבקר.

יש לי מחשב בעמדת אריזה, לידו אשים את הרמזור, כך שאת המתח הוא יקבל דרך חיבור ה-USB. ואם כבר יחובר דרך USB, אז אפשר יהיה להעביר את הנתונים דרך החיבור, מה שמפשט את הפרוייקט, לא צריך להוסיף יכולות תקשורת למיקרובקר.

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

החלוקה שחשבתי עליה היא כזו:
מערכת האתר תעביר את הנתונים הגולמיים, שזה כמות ההזמנות לפי המצב שלהם ולפי צורת המשלוח, כלומר כמות ההזמנות ל-Boxit שממתינות לאישור, כמות ל-BoxIt ששמורות במלאי, כמות ל-Boxit שבהכנה ואותו הדבר עבור הזמנות ל-FedEx, לדואר רשום וכו'. אולי אפילו אוסיף סיכום של כמה דברים, כמו סה"כ הזמנות לכל מצב של הזמנה.

במקרה הזה מי שיקרא את הנתונים הוא מחשב ואני יכול להעביר את הנתונים בפורמט XML, אבל מי יודע, אולי בעתיד ארצה להשתמש באותם הנתונים לדברים אחרים, כאשר מיקרובקר הוא זה שיבקש אותם, אז יהיה לו קשה לנתח את ה-XML, כך שכנראה שאקודד את הנתונים בצורה פשוטה יותר. יש פורמט ידוע הנקרא TLV, שאלה הם ראשי תיבות של Type, Length, Value. כלומר ה-Type מציין את סוג הנתון, ה-Length מציין את אורך הנתון שיגיע אחריו וה-Value הוא הנתון עצמו. במקרים בהם אורך הנתון הוא קבוע לרוב ה-Types, אז אפשר לוותר על ציון האורך.
במקרה שלי נראה לי שבתור Type אשתמש בכמה אותיות (2-3), אוותר על ה-Length ואת הנתון אשלח באורך של Byte אחד, כך שגם למיקרובקר הפשוט של 8bit יהיה קל יחסית לטפל בנתונים (כשאגיע למצב שאני צריך לראות שיש יותר מ-255 הזמנות שדורשות טיפול, כנראה שיהיה לי הרבה זמן לשדרג את המערכת :roll: ). ליתר בטחון אצטרך להוסיף הגנה שאם הנתון גדול מ-254, אז לשלוח תמיד 255). את הנתונים אפשר להפריד ע"י פסיקים.
בדרך כלל נהוג גם לכונן את ה-TLVs אחד בתוך השני, כלומר לעשות Type שהוא נגיד מאגד את כל ההזמנות שבמצב "שמור במלאי" ובתוכו לעשות עוד Type פנימי לכל סוגי המשלוח. סידור זה מאוד הגיוני, אבל יכול להקשות על פיענוח הנתונים, כך שאני לא רוצה להשתמש בשיטה זו.

אז יש לנו עמוד שיוכל לספק את הנתונים הגולמיים בצד אחד. בצד השני יש מיקרובקר שצריך לשלוט על התצוגה והייתי רוצה שזה כל מה שהוא ידע לעשות. יגידו לו להדליק את הלד האדום קבוע ולהבהב בלד הכחול בקצב של פעם בשניה, אז זה מה שהוא יעשה. אם בעתיד ארצה להוסיף זמזם או תצוגה, אז בכל מקרה צריך לעשות שינויים ברכיב הזה, כך שאוכל להוסיף את היכולות האלה גם בתוכנה שלו.

נשאר הרכיב השלישי, המחשב שידגום את האתר כל פרק זמן כלשהו וישלח את הפקודות לבקר הלדים של הרמזור. זהו המתווך בין הנתונים הגולמיים לבין הדלקת הלדים. כאן תהיה כל הלוגיקה של מה להדליק ובאיזו צורה. אם צריך לצפצף בזמזם, אז החלק הזה של הפרוייקט צריך להבין שכמות ההזמנות הדחופות שמחכות לטיפול עלתה ושצריך להשמיע צפצוף בצליל X למשך זמן Y. זה החלק שצריך לעבד את הנתונים ולהבין שמשלוחים ל-BoxIt ול-FedEx מדליקים את אותו הלד.

זהו זה בינתיים. נראה לי כיסיתי את כל מה שחשבתי עליו. בפוסטים הבאים אנסה לתעד את מהלך הבניה וכתיבת הקוד.
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

טוב...

התחלתי מהצד של השרת כי אני בבית ואפשר לעשות את זה מרחוק.

האתר שלי בנוי על בסיס נתונים MySQL וכל הקוד כתוב בשפת PHP, שזו הספה שמשתמשים מה ביותר מ-80% משרתי WEB (מקור למספר).
אני מאוד אוהב את ה-PHP ומעדיף אותו על פני שפות סקריפטיות אחרות מהרבה סיבות. הסיבה העיקרית היא שה-Syntax דומה מאוד לשפת C, שמבחינתי זו שפת האם לכל מה שקשור לתכנות. בהשוואה ל-Python למשל אין את כל הסיבוך המיותר עם סידור ה-Tabs בתחילת השורות, אני רוצה להתרכז בפונקציונליות ולא בעיצוב כדי שהשורות יהיו מיושרות, אחרת הקוד לא יעשה את מה שאני רוצה שיעשה...

הוספתי עמוד נוסף במערכת הניהול הפנימית של האתר שכל פעם שניגשים אליו הוא דוגם את בסיס הנתונים ומחזיר את הנתונים שרציתי.

הנתונים מוחזרים בפורמט TLV שהזכרתי, בצורת Text קריא, ה-Type הוא כמה אותיות (שאסביר בהמשך), החלטתי בכל זאת להוסיף את ה-Length וה-Value הוא הערך המספרי של הנתון באורך של Length תווים. כדי שאפשר יהיה לקודד את המספרים באורך קבוע, הנתון עצמו הוא ב-HEX, כך שלמספר באורך של Byte צריך 2 תווית, 2 בתים זה 4 תווים ו-4 בתים זה 8 תווים, כולם עם אפסים לפני המספר עצמו כדי שיהיה באורך קבוע.

כרגע לא ממש צריך את ה-Length כי כל המידע הוא באורך קבוע בהתאם ל-Type שלו, אבל החלטתי בכל זאת להוסיף אות אחת של אורך, אולי זה יעזור לי לנהל את הזכרון בצורה חכמה יותר בצד שמקבל את המידע וצריך לטפל בו או שאם בעתיד אצטרך לשדר את אותו המידע פעמיים, נניח פעם עם מידע באורך Byte אחד לטובת מיקרובקר חלש של 8bit ופעם השניה עם אותו המידע באורך 4Bytes שיטופל ע"י מעבד חזק, אז השדה של אורך הוא זה שיבדיל ביניהם. המיקרובקר החלש יוכל לדלג על המידע עם אורך שלא מתאים לו. בסופו של דבר זה רק תו אחד לכל חתיכת מידע, כך שזה לא כזה "יקר".

הנתונים יופרדו ע"י "," (פסיק). בהתחלה של הנתונים ישלח "OK", כך שהצד המקבל יכול לוודא שהוא מקבל משהו תקין ובסוף ישלח "END", סתם למען הסדר הטוב.

המידע שיוחזר הוא כמות של הזמנות לפי סוג משלוח ומצב הזמנה כלשהו, הוספתי גם את מספר ההזמנה האחרונה שמתאימה לשילוב זה של הפילטר כדי שאפשר יהיה להוסיף זמזום כשמגיעה הזמנה חדשה. ויש גם מידע על הזמנות רק לפי מצב ההזמנה (כאלה שבהכנה וכאלה שדורשים טיפול מיוחד כמו בקשות להצעות מחיר והזמנות ייצור של מעגלים מודפסים).
אז ה-Types שבחרתי הם כאלה:
שתי האותיות הראשונות יציינו את סוג המידע, DT למידע לפי צורת משלוח (Delivery Type), אחריהם אות אחת שתציין את סוג המשלוח (R-רשום, E-אקספרס, B-בוקסיט וכו'), אחריה אות שתציין מצב ההזמנה (W-מחכה לאישור, S-שמור במלאי וכו'), אחריה אות שתציין אם הנתון הוא כמות (C) או מספר של ההזמנה האחרונה (L), אחריה ספרה שתציין את כמות התווים במידע עצמו (Length) ואחריה המידה באורך התווים שצויין בפורמט Hexadecimal.

למידה לפי מצב ההזמנה שתי האותיות הראשונות יהיו "OS" שזה הקיצור של (Order Status), אחריהם אות שתציין את המצב (X-Exception או R ל-pReparing), אחריה C או L ל-Count או Last, אחריה ספרה שתציין את כמות התווים במידע עצמו (Length) ואחריה המידה באורך התווים שצויין בפורמט Hexadecimal.

זה רק נשמע מסובך, אבל זה לא ואחרי שמכירים את הפורמט אפשר להסתכל על הפלט ולהבין את כל הנתונים. בכל מקרה כל זה מיוצר ע"י קוד ומפוענח ע"י תוכנה בצד השני, אז לא ממש צריך לזכור את זה.

נכון לעכשיו הפלט נראה ככה:

קוד: בחירת הכל

OK,DTMWC40000,DTMWL800000000,DTMSC40000,DTMSL800000000,DTMPC40000,DTMPL800000000,DTRWC40000,DTRWL800000000,DTRSC40000,DTRSL800000000,DTRPC40000,DTRPL800000000,DTEWC40000,DTEWL800000000,DTESC40000,DTESL800000000,DTEPC40000,DTEPL800000000,DTBWC40000,DTBWL800000000,DTBSC40000,DTBSL800000000,DTBPC40000,DTBPL800000000,DTSWC40000,DTSWL800000000,DTSSC40001,DTSSL80000425E,DTSPC40000,DTSPL800000000,OSRC40000,OSRL800000000,OSXC40003,OSXL80000425F,END
והנה הקוד שעושה את כל זה אחרי שהורדתי ממנו את הסיבוך של גישה לבסיס נתונים:

קוד: בחירת הכל

<?php

// Respond with OK
print('OK,');

// Get delivery types from DB
while ($dt = NextDeliveryType)
{
	// Count number of orders for each delivery type
	// In waiting approval state
	OrdersByDeliveryType($dt, $orderStatus['WaitingApproval'], 'W');
	
	// Saved state
	OrdersByDeliveryType($dt, $orderStatus['Saved'], 'S');

	// Payed state
	OrdersByDeliveryType($dt, $orderStatus['Payed'], 'P');
}

OrdersByStatus($orderStatus['Preparing'], 'R');

OrdersWaitingWithException('X');

print('END');


function OrdersByDeliveryType($dt, $status, $prefix)
{
	// Get CustomerOrder from the DB with specified Delivery Type and Status
	$row = DB_Result

	$count = $row['count'];
	if($count >= 0xFFFF-1)
	{
		$count = 0xFFFF;
	}
	
	// DT[DTP][STP]C4[V]
	// DT = Constant 'DT' for Delivery Type
	// [DTP] = Delivery type prefix (1 letter)
	// [STP] = Status prefix (1 letter)
	// C = Constant 'C' means count
	// 4 = Constant '4' means 4 characters (2 bytes hex value)
	// [V] = value in 2 byte HEX (4 characters)
	print('DT' . $dt['Prefix'][0] . $prefix[0] . 'C4' . sprintf('%04X', $count) . ',');

	// Get last order number for that delivery type and order status
	$row = DB_Result

	$last = $row['last'];
	
	// DT[DTP][STP]L8[V]
	// DT = Constant 'DT' for Delivery Type
	// [DTP] = Delivery type prefix (1 letter)
	// [STP] = Status prefix (1 letter)
	// L = Constant 'L' means last
	// 8 = Constant '8' means 8 characters (4 bytes value)
	// [V] = value in 4 byte HEX (8 characters)
	print('DT' . $dt['Prefix'][0] . $prefix[0] . 'L8' . sprintf('%08X', $last) . ',');
}

function OrdersByStatus($status, $prefix)
{
	// Get CustomerOrder with specified status
	$row = DB_Result

	$count = $row['count'];
	if($count >= 0xFFFF-1)
	{
		$count = 0xFFFF;
	}
	
	// OS[P]C4[V]
	// OS = Constant 'OS' for Order Status
	// [P] = Order status prefix (1 letter)
	// C = Constant 'C' means count
	// 4 = Constant '4' means 4 characters (2 bytes hex value)
	// [V] = value in 2 byte HEX (4 characters)
	print('OS' . $prefix[0] . 'C4' . sprintf('%04X', $count) . ',');

	// Get last order number for that delivery type and order status
	$row = DB_Result

	$last = $row['last'];
	
	// OS[P]L8[V]
	// OS = Constant 'OS' for Order Status
	// [P] = Delivery type prefix (1 letter)
	// L = Constant 'L' means last
	// 8 = Constant '8' means 8 characters (4 bytes value)
	// [V] = value in 4 byte HEX (8 characters)
	print('OS' . $prefix[0] . 'L8' . sprintf('%08X', $last) . ',');
}

function OrdersWaitingWithException($prefix)
{
	// Get CustomerOrder with exception flag
	$row = DB_Result

	$count = $row['count'];
	if($count >= 0xFFFF-1)
	{
		$count = 0xFFFF;
	}
	
	// OS[P]C4[V]
	// OS = Constant 'OS' for Order Status
	// [P] = Order status prefix (1 letter)
	// C = Constant 'C' means count
	// 4 = Constant '4' means 4 characters (2 bytes hex value)
	// [V] = value in 2 byte HEX (4 characters)
	print('OS' . $prefix[0] . 'C4' . sprintf('%04X', $count) . ',');

	// Get last order number for orders with exception
	$row = DB_Result

	$last = $row['last'];
	
	// OS[P]L8[V]
	// OS = Constant 'OS' for Order Status
	// [P] = Delivery type prefix (1 letter)
	// L = Constant 'L' means last
	// 8 = Constant '8' means 8 characters (4 bytes value)
	// [V] = value in 4 byte HEX (8 characters)
	print('OS' . $prefix[0] . 'L8' . sprintf('%08X', $last) . ',');
}

?>
חלק של השרת מוכן. המשך בפוסט הבא...
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

המשך...

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

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

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

אז הכנתי לי את כל הרכיבים, חיברתי את הכל (תיכף אפרט) והרמזור מתחיל לקבל צורה:
תמונה

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

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

אם אתם שואלים את עצמכם למה לא חיברתי את כל הלדים באותו הצבע יחד, אז הסיבה היא שהרכיב MAX7219 שאני רוצה להשתמש בו יכול לספק עד 40mA לכל חיבור. כל לד בסיסי צריך לקבל זרם של 20mA, כך שאם הייתי מחבר את שלושתם יחד, הייתי צריך לספק 60mA כדי שידלקו כמו שצריך. מה גם, מכיוון ש-MAX7219 לא מדליק את הלדים בצורה קבועה, אלא "סורק" אותם, כלומר מדליק כל אחד מה-64 לדים שהוא יכול לטפל בהם לפרקי זמן קצרים, אז אפשר לספק ללדים הפשוטים גם 40mA כדי לקבל חיווי חזק יותר.
מה שכן היה אפשר לעשות, זה לחבר את כל המינוסים של הלדים באותו הצבע יחד ואת הפלוסים לכל ערוץ של MAX7219 בנפרד, מה שאולי היה חוסך קצת חיווט (4 חוטים ל-3 לדים במקום 6), אבל העדפתי להפריד. גם ככה היה די צפוף להכניס את הלדים למקום, אם גם היו מולחמים ביניהם, אז כנראה שהייתי צריך קודם להכניס אותם ואז להלחים אותם במקום, מה שלא יהיה נוח בגלל הצפיפות וגם כי אני לא אוהב לקבע יותר מדי את הדברים במקום.

למרות שהיו לי במקרה ברגים עם ראש שטוח שאמורים היו להתאים לבסיס העגול, הם בכל זאת בלטו קצת והרמזור לא היה יציב. פשוט לקחתי 4 ברגים רגילים שישמשו כרגליות קטנות:
תמונה

שלב הבא - הבקר וה-MAX7219.
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

היה לי עוד קצת זמן ביום שישי כדי להמשיך עם הפרוייקט.

לקחתי את התפסנים לארדואינו כדי שיחזיקו את המעגל בצד האחורי של הפרופיל:
תמונה

רוחב הארדואינו הוא 53 מ"מ, לי אין לוחות הלחמה בגודל הזה, כך שלקחתי את הלוח בגודל 6x8 ס"מ וגזרתי אותו לגודל הרצוי עם מספרי מטבח (הסתבר שהלוח הרבה יותר קשיח ממה שחשבתי וזה לא כל כך קל...):
תמונה

רציתי שהחוטים של הלדים יתחברו למעגל מבפנים, כך זה יראה יותר מסודר, אז בדקתי ש-6 מחברים שמוצמדים אחד לשני נכנסים יפה לרוחב הפרופיל, הלחמתי 2 שורות של 6 מחברים ועוד שורה של 3.
תמונה

מיהרתי הביתה, אז הלחמתי את הכל כמה שיותר מהר, הספקתי רק לבדוק שאדמה ו-VCC לא מחוברים ביניהם.
לקחתי את הרמזור, המעגל שהלחמתי ורב מודד הביתה כדי לנסות להפעיל אותו בסוף השבוע כשיהיה לי קצת זמן פנוי...

בבית גיליתי שצד הנקבה של המחבר רחב יותר מהזכרים שמולחמים למעגל, כך שאי אפשר לחבר יותר מ-2-3 מחברים צמודים :shock:
חיברתי רק כמה לדים כדי לראות אם אני מצליח לשלוט בהם... גם כאן גיליתי שפספסתי קו DIG0 והתחלתי להלחים את החוטים לקווים DIG1, DIG2 וכו'.
לא אסון, אבל לקח לי כמה ניסיונות להבין למה אני לא מצליח להפעיל שורה של לדים שרציתי. אפשר לכתוב את התוכנה בהתאם, אבל אם כבר אצטרך לפרק את המחברים, אחבר את הכל החל מקו DIG0 והלאה.
ככה זה כשממהרים...

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

יש מלא שרטוטים ברשת איך לחבר את MAX7219, העיין ננעלה על השרטוט הזה כשחיפשתי בגוגל:
תמונה
התמונה מגיעה מכאן.

התוכנית עכשיו היא לפרק את המחברים כדי לסדר אותם עם מרווחים כדי שאפשר יהיה לחבר את הכל כמו שצריך.
את הלדים צריך לחבר בצורת מטריצה, כך שכל הקתודות של לדים באותו הצד צריכות להיות מחוברות יחד, כל האנודות של כל שורה גם יחד וכו'.
אחרי שחשבתי על זה קצת, אם אסובב את המחברים ב-90 מעלות, אפשר יהיה כנראה להלחים את החיבורים המשותפים בצורה נוחה יותר בלי יותר מדי חוטים בולטים.
אם לא אצליח לפרק את המחברים, אצטרך לזרוק את המעגל וסוקטים שמולחמים אליו. זו עוד דוגמה מצויינת למה צריך להשקיע בסוקטים ולא להלחים את הרכיבים ישירות למעגל!

אז מה למדתי תוך כדי:
  • מעגלים מודפסים הרבה יותר קשיחים ממה שחשבתי, היה די קשה לחתוך אותו עם מספרי מטבח
  • המלחם והבדיל מימי הבית ספר (סביבות 25 שנה) עובדים מצויין
  • לעשות גשרים עם בדיל בין שתי נקודות סמוכות של מעגל מודפס הרבה יותר קשה ממה שחשבתי
  • לבדוק טוב טוב לפני שמלחימים משהו למעגל
  • לא למהר
  • חייב להביא יד שלישית כדי שיהיה יותר נוח לעבוד
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

בסוף הצלחתי לפרק את המחברים, אבל מרוב ה-Flux והחום של המלחם האיזור לא נראה טוב בכלל. נוסיף לזה את זה שמצאתי שחיברתי כמה דברים לא נכון, אז ויתרתי על הלוח הזה והתחלתי הכל מחדש.

הפעם הלחמתי את המחברים בצורה אחרת:
תמונה

ומאחורה עשיתי פיסול מחוטים כדי לייצר מטריצה:
תמונה

וככה זה נראה עם כל החיבורים:
תמונה

בדיעבד, הייתי צריך להשתמש בחוט יותר עבה לגשרים כי הוא מתכופף בקלות.
כנראה שאוסיף קצת דבק חם על האיזור הזה כדי להיות בטוח שלא יהיו קצרים בעתיד.

כתבתי קוד פשוט לבדיקה שהכל עובד. כבר לא זוכר על איזה קוד התבססתי. היה לי משהו מוכן שהשתמשתי בעבר, קצת שינויים והכל נראה עובד. ניסיתי גם להדליק צבע בודד, לד בודד וכו' כדי לוודא שהכל עובד.

זה הקוד:

קוד: בחירת הכל

int const led = 13;

int const DATA_PIN = 10;
int const CLOCK_PIN = 11;
int const LOAD_PIN = 12;

// MAX7219 registers
byte digit0Register = 0x01;
byte digit1Register = 0x02;
byte digit2Register = 0x03;
byte digit3Register = 0x04;
byte digit4Register = 0x05;
byte digit5Register = 0x06;
byte digit6Register = 0x07;
byte digit7Register = 0x08;
byte decodeModeRegister = 0x09;
byte intensityRegister = 0x0A;
byte scanLimitRegister = 0x0B;
byte operationRegister = 0x0C;
byte ledTestRegister = 0x0F;

// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     

//  Serial.begin(9600);
  
  pinMode(DATA_PIN, OUTPUT);     
  pinMode(CLOCK_PIN, OUTPUT);     
  pinMode(LOAD_PIN, OUTPUT);     

  digitalWrite(DATA_PIN, LOW);
  digitalWrite(CLOCK_PIN, LOW);
  digitalWrite(LOAD_PIN, LOW);


  WriteCommand(decodeModeRegister, 0);
  WriteCommand(scanLimitRegister, 4);    // digits 0-4
  WriteCommand(intensityRegister, 0x0F);

  WriteCommand(operationRegister, 1);  
  WriteCommand(ledTestRegister, 0);  
 
}

int state = 0;

void loop() {
  if(state == 0)
  {

    WriteCommand(digit0Register, 0);
    WriteCommand(digit1Register, 0);
    WriteCommand(digit2Register, 0);
    WriteCommand(digit3Register, 0);
    WriteCommand(digit4Register, 0);

    digitalWrite(led, 0);
    state = 1;
  }
  else
  {
    
    WriteCommand(digit0Register, 0x70);
    WriteCommand(digit1Register, 0x70);
    WriteCommand(digit2Register, 0x70);
    WriteCommand(digit3Register, 0x70);
    WriteCommand(digit4Register, 0x70);

    digitalWrite(led, 1);
    state = 0;
  }
  
  delay(3000);
}

void WriteCommand(byte reg, byte val)
{
  digitalWrite(LOAD_PIN, LOW);

  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, reg);
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, val);
  
  digitalWrite(LOAD_PIN, HIGH);
}
בעצם סיימתי עם הצד החומרתי של הפרוייקט. נשאר לכתוב את הקוד האמיתי למיקרובקר וסקריפט בשרת שיתרגם את מה שהוא מקבל מהאתר לפקודות הדלקת הלדים.
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

טוב... כתבתי את הצד של הרמזור בארדואינו A-Star.

הרמזור יקבל מחרוזת של 2 תווים לכל צבע בסדר הזה: RRYYGGBBWW, כאשר שני התווים הם מספר HEX, כך שכל צבע יוכל לקבל BYTE אחד של מידע.
ל-BYTE זה יהיו 3 משמעויות...
כאשר הוא 0, הלד יהיה כבוי.
כאשר הוא 0xFF, הלד יהיה דלק קבוע.
כאשר הוא מספר כלשהו בין 1 ל-254, אז המספר יציין את קצב ההבהוב ביחידות של 100mSec.
כל סימן אחר שהוא לא מספק או אות ב-HEX יאפסו את הטיפול במחרוזת.

הרעיון בקוד הוא שיש 2 משתנים לכל צבע, אחד שומר ערך הנוכחי של ספירה לטובת ההבהוב והשני המספר שצריך להגיע אליו כדי להחליף את המצב של הלד (זה מה שקובע את הקצב).
כשצריך להבהב בלד, הקוד מוסיף 1 לערך הרלוונטי כל 100mSec, אם הערך עבר את ה-Limit, משנים את הערך למספר שלילי של ה-Limit.
הלד עצמו דולק רק כשהערך חיובי.
אם לדוגמה ה-Limit הוא 10 (הבהוב של פעם בשניה), אז 10 הערך ינוע ממינוס 10 עד 10.

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

עוד משהו שיש בקוד זה טיפול בחיווי כאשר אין תקשורת עם הרמזור במשך יותר מ-5 דקות. החיווי הוא ריצה של הלדים מלמעלה למטה. זה מצב שלא יכול לקרות במצב עבודה רגיל, כך שצריך טיפול מיוחד בהבהוב המיוחד הזה.

אז הנה הקוד של ה-Arduino:

קוד: בחירת הכל

byte const LED_PIN = 13;

byte const DATA_PIN = 10;
byte const CLOCK_PIN = 11;
byte const LOAD_PIN = 12;

// MAX7219 registers
byte const digit0Register = 0x01;
byte const digit1Register = 0x02;
byte const digit2Register = 0x03;
byte const digit3Register = 0x04;
byte const digit4Register = 0x05;
byte const digit5Register = 0x06;
byte const digit6Register = 0x07;
byte const digit7Register = 0x08;
byte const decodeModeRegister = 0x09;
byte const intensityRegister = 0x0A;
byte const scanLimitRegister = 0x0B;
byte const operationRegister = 0x0C;
byte const ledTestRegister = 0x0F;

byte const ALL_LEDS = 0x70;
byte const RED_LED = digit4Register;
byte const YELLOW_LED = digit3Register;
byte const GREEN_LED = digit2Register;
byte const BLUE_LED = digit1Register;
byte const WHITE_LED = digit0Register;

int redValue = 0;
int redLimit = 0;
int yellowValue = 0;
int yellowLimit = 0;
int greenValue = 0;
int greenLimit = 0;
int blueValue = 0;
int blueLimit = 0;
int whiteValue = 0;
int whiteLimit = 0;

int blinkSync = 0;
int const blinkSyncLimit = 10;

int comTimeout = 0;
int const comTimeoutLimit = 3000;  // 3000=5 minutes

void setup() {
  // initialize the digital pin as an output.
  pinMode(LED_PIN, OUTPUT);     

  Serial.begin(9600);
  
  pinMode(DATA_PIN, OUTPUT);     
  pinMode(CLOCK_PIN, OUTPUT);     
  pinMode(LOAD_PIN, OUTPUT);     

  digitalWrite(DATA_PIN, LOW);
  digitalWrite(CLOCK_PIN, LOW);
  digitalWrite(LOAD_PIN, LOW);

  WriteCommand(decodeModeRegister, 0);
  WriteCommand(scanLimitRegister, 4);    // digits 0-4
  WriteCommand(intensityRegister, 0x0F);

  WriteCommand(operationRegister, 1);  
  WriteCommand(ledTestRegister, 0);  
}

// The communication will be done using a string of HEX bytes in the following form:
// <delimiter>RRYYGGBBWW<delimiter> where:
// <delimiter> can be "," or "|"
// RR = Red led command
// YY = Yellow led command
// GG = Green led command
// BB = Blue led command
// WW = White led command
// While the command can be: 0x00=OFF, 0xFF=ON, other numbers are the blinking rate in 100mSecconds intervals
// Meaning that in order to blink once in a second the number should be 0x0A
void loop() {
  int b1, b2;
  
  comTimeout++;
  if(comTimeout == comTimeoutLimit)
  {
    // First time in timeout, set led values to a pattern that will run the lights from up to down
    // Limits are set to a large number, so they won't interfere with the logic
    redValue = 0;
    yellowValue = -5;
    greenValue = -10;
    blueValue = -15;
    whiteValue = -20;
    
    redLimit = 100;
    yellowLimit = 100;
    greenLimit = 100;
    blueLimit = 100;
    whiteLimit = 100;
  }
  if(comTimeout > comTimeoutLimit)
  {
    // Maintain the light running down logic
    if(redValue > 5) redValue -= 25;
    if(yellowValue > 5) yellowValue -= 25;
    if(greenValue > 5) greenValue -= 25;
    if(blueValue > 5) blueValue -= 25;
    if(whiteValue > 5) whiteValue -= 25;
    
    // Keep the comTimeout value larger than the comTimeoutLimit until its reset by processing of the serial input
    comTimeout = comTimeoutLimit+1;
  }
  
  // Process the input if any
  while (Serial.available() > 0) 
  {
    comTimeout = 0;
    digitalWrite(LED_PIN, HIGH);
    
    // Red led
    b1 = Serial.read();
    b2 = Serial.read();
Serial.print(b1, HEX);
Serial.print(b2, HEX);
    if(!ValidInput(b1, b2)) { Serial.flush(); break; }
    SetLimit(redValue, redLimit, Hex2Int(b1, b2));

    // Yellow led
    b1 = Serial.read();
    b2 = Serial.read();
Serial.print(b1, HEX);
Serial.print(b2, HEX);
    if(!ValidInput(b1, b2)) { Serial.flush(); break; }
    SetLimit(yellowValue, yellowLimit, Hex2Int(b1, b2));

    // Green led
    b1 = Serial.read();
    b2 = Serial.read();
Serial.print(b1, HEX);
Serial.print(b2, HEX);
    if(!ValidInput(b1, b2)) { Serial.flush(); break; }
    SetLimit(greenValue, greenLimit, Hex2Int(b1, b2));

    // Blue led
    b1 = Serial.read();
    b2 = Serial.read();
Serial.print(b1, HEX);
Serial.print(b2, HEX);
    if(!ValidInput(b1, b2)) { Serial.flush(); break; }
    SetLimit(blueValue, blueLimit, Hex2Int(b1, b2));

    // White led
    b1 = Serial.read();
    b2 = Serial.read();
Serial.print(b1, HEX);
Serial.print(b2, HEX);
    if(!ValidInput(b1, b2)) { Serial.flush(); break; }
    SetLimit(whiteValue, whiteLimit, Hex2Int(b1, b2));
Serial.println("");  
  }
  digitalWrite(LED_PIN, LOW);

  // Process the leds every 100mSec
  ProcessTheLeds();
  
  blinkSync++;
  if(blinkSync >= blinkSyncLimit)
  {
    blinkSync = -blinkSyncLimit;
  }
  
  delay(100);
}

void ProcessTheLeds()
{
  ProcessLed(RED_LED, redValue, redLimit);
  ProcessLed(YELLOW_LED, yellowValue, yellowLimit);
  ProcessLed(GREEN_LED, greenValue, greenLimit);
  ProcessLed(BLUE_LED, blueValue, blueLimit);
  ProcessLed(WHITE_LED, whiteValue, whiteLimit);
}

void ProcessLed(int const reg, int &value, int &limit)
{
  if(limit == 0)
  {
    value = 0;
  }
  else if(limit == 0xFF)
  {
    value = 1;
  }
  else
  {
    value +=1;
    if(value >= limit)
    {
      value = -limit;
    }
  }
  
  if(value > 0)
  {
    WriteCommand(reg, ALL_LEDS);
  }
  else
  {
    WriteCommand(reg, 0);
  }
}

void SetLimit(int &value, int &limit, int i)
{
  // Switching from ON or OFF to blinking
  if((i != 0 && i != 0xFF) && (limit == 0 || limit == 0xFF))
  {
    // Initiate the value to have more or less syncronous blink
    value = blinkSync;
  }
  
  limit = i;
}

int Hex2Int(int b1, int b2)
{
  int val = 0;
  
  if(b1 >= '0' && b1 <= '9')
  {
    val = (b1-'0')<<4;
  }
  else if(b1 >= 'A' && b1 <= 'F')
  {
    val = (b1-'A'+10)<<4;
  }
  else if(b1 >= 'a' && b1 <= 'f')
  {
    val = (b1-'a'+10)<<4;
  }

  if(b2 >= '0' && b2 <= '9')
  {
    val += b2-'0';
  }
  else if(b2 >= 'A' && b2 <= 'F')
  {
    val += b2-'A'+10;
  }
  else if(b2 >= 'a' && b2 <= 'f')
  {
    val += b2-'a'+10;
  }
  
  return val;
}

bool ValidInput(int b1, int b2)
{
  bool valid = false;
  
  if(b1 >= '0' && b1 <= '9' || b1 >= 'a' && b1 <= 'f' || b1 >= 'A' && b1 <= 'F')
  {
    if(b2 >= '0' && b2 <= '9' || b2 >= 'a' && b2 <= 'f' || b2 >= 'A' && b2 <= 'F')
    {
      valid = true;
    }
  }
 
  return valid; 
}

void WriteCommand(byte reg, byte val)
{
  digitalWrite(LOAD_PIN, LOW);

  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, reg);
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, val);
  
  digitalWrite(LOAD_PIN, HIGH);
}
חיברתי את הרמזור למחשב עמדת האריזות. כל המחשבים אצלי מריצים לינוקס, כך שכתבתי סקריפט פשוט שרץ אוטומטית כשהמחשב עולה.
הסקריפט בודק שיש חיבור טורי בשם ttyACM0 שנוצר אחרי שמחברים את הארדואינו (נכון, תהיה בעיה אם יהיה עוד חיבור דומה, בינתיים זה עובד, אם יהיה צורך אטפל בזה בהמשך), אם החיבור קיים, מבקש מהשרת המשרדי שיביא לו את הפקודות לרמזור ואם יש תשובה, מעביר אותה ל-Arduino דרך החיבור הטורי. מחכה 20 שניות ועושה שוב את הסיבוב.
בלינוקס יש גם מנגנון cron שיכול להריץ סקריפטים כל פרק זמן קבוע, אבל הזמן המינימלי שלו הוא דקה. רציתי משהו קצת יותר מהיר, אז סקריפט עצמאי במצב הזה הוא הכיוון היחיד.

זה הסקריפט:

קוד: בחירת הכל

#!/bin/bash
DEVICE="ttyACM0"
PORT="/dev/$DEVICE"
FILE="/sys/class/tty/$DEVICE/dev"

while true
do
  if [ -f $FILE ]; then
    content=$(wget --no-check-certificate -O - https://Office-Server/lights_indicator.php -q -O -)
    echo $content

    if [[ ! -z $content ]]; then
      echo $content > $PORT
    fi
  else
    echo "Device $DEVICE file not found: $FILE"
  fi

  sleep 20
done
השלב האחרון שנשאר זה לכתוב את הקוד בשרת המשרדי שימשוך את מצב ההזמנות ממערכת האתר, יחשב איזה לד צריך להיות דלוק באיזו צורה ולהחזיר את המחרוזת שתגיע לרמזור ותדליק את הלדים...
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

ושלב האחרון... התוכנה בשרת המשרדי, שזה בעצם קוד PHP שמתבצע כל פעם שהמחשב עם הרמזור ניגש אליו.

הקוד נראה ארוך בגלל ה-Parser שמימשתי כפונקציה לכל רמת מידע במחרוזות שמוחזרות מהמערכת, אבל הוא לא כזה מסובך.
כמו שנהוג לעשות ב-PHP, ה-Parsing נעשה לתוך מערך רב-מימדי עם כל המידע ואחרי זה הלוגיקה ניגשת למקומות הרצויים במערך הזה כדי לקבל החלטות.
לבסוף מדפיסים את המחרוזת שצריכה להגיע לרמזור בהתאם להחלטות.

בינתיים לא ראיתי בעיות עם ההזמנות שהיו אתמול והיום, אמשיך לעקוב ואתקן אם אמצע איזה באג.

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

אז הנה הקוד:

קוד: בחירת הכל

<?php
session_start();

const LED_ON = 0xFF;
const LED_OFF = 0;
const LED_BLINK_1HZ = 0x0A;


$content = file_get_contents('URL to order status page');
if($content === FALSE)
{
	// Something went wrong
	die();
}

$contentArr = explode(',', $content);
if($contentArr[0] != 'OK')
{
	// That's not that I've expected to get...
	die();
}

/*
if(isset($_SESSION['SavedOrdersData']))
{
	$oldData = $_SESSION['SavedOrdersData'];
}
else
{
	$oldData = array();
}
*/

// Parsing the data
$newData = array();
ParseContent($newData, $contentArr);


// Implement the logic
$leds = array();

///// Urgent = Red
// There are Self-Pickup orders waiting for approval
if($newData['DeliveryType']['Self']['WaitingApproval']['Count'] > 0 || $newData['DeliveryType']['Self']['Payed']['Count'] > 0)
{
	$leds['Red'] = LED_BLINK_1HZ;
}

// Approved Self-Pickup orders
if($newData['DeliveryType']['Self']['Saved']['Count'] > 0)
{
	$leds['Red'] = LED_ON;
}

///// Medium = Yellow
// There are Express or Boxit orders waiting for approval
if($newData['DeliveryType']['Express']['WaitingApproval']['Count'] > 0 || $newData['DeliveryType']['Express']['Payed']['Count'] > 0 ||
	$newData['DeliveryType']['Boxit']['WaitingApproval']['Count'] > 0 || $newData['DeliveryType']['Boxit']['Payed']['Count'] > 0)
{
	$leds['Yellow'] = LED_BLINK_1HZ;
}

// Approved Express or Boxit orders
if($newData['DeliveryType']['Express']['Saved']['Count'] > 0 || $newData['DeliveryType']['Boxit']['Saved']['Count'] > 0)
{
	$leds['Yellow'] = LED_ON;
}

///// Low = Green
// There are Registered or Mail24 orders waiting for approval
if($newData['DeliveryType']['Registered']['WaitingApproval']['Count'] > 0 || $newData['DeliveryType']['Registered']['Payed']['Count'] > 0 ||
	$newData['DeliveryType']['Mail24']['WaitingApproval']['Count'] > 0 || $newData['DeliveryType']['Mail24']['Payed']['Count'] > 0)
{
	$leds['Green'] = LED_BLINK_1HZ;
}

// Approved Registered or Mail24 orders
if($newData['DeliveryType']['Registered']['Saved']['Count'] > 0 || $newData['DeliveryType']['Mail24']['Saved']['Count'] > 0)
{
	$leds['Green'] = LED_ON;
}

///// Preparing = Blue
// There are orders in preparing state
if($newData['OrderStatus']['Preparing']['Count'] > 0)
{
	$leds['Blue'] = LED_ON;
}

///// Exception = White
// There are orders in exception state
if($newData['OrderStatus']['Exception']['Count'] > 0)
{
	$leds['White'] = LED_BLINK_1HZ;
}

// Output the leds
PrintLedCommand($leds['Red']);
PrintLedCommand($leds['Yellow']);
PrintLedCommand($leds['Green']);
PrintLedCommand($leds['Blue']);
PrintLedCommand($leds['White']);
print(",\n");

function PrintLedCommand($val)
{
	printf("%02X", $val);
}

function ParseContent(&$arr, $contentArr)
{
	foreach($contentArr as $str)
	{
		switch(substr($str, 0, 2))
		{
			case 'OK':
			break;

			case 'DT':	// Delivery Type
				ParseDeliveryType($arr['DeliveryType'], substr($str, 2));
				break;

			case 'OS':	// Order Status
				ParseOrderStatus($arr['OrderStatus'], substr($str, 2));
				break;

			case 'EN':	// End indication
				break;
				
			default:
				die('!Unknown content type: ' . substr($str, 0, 2));
		}
	}
}

function ParseOrderStatus(&$arr, $str)
{
	switch(substr($str, 0, 1))
	{
		case 'R':	// Preparing
			ParseValueType($arr['Preparing'], substr($str, 1));
			break;

		case 'X':	// Exception
			ParseValueType($arr['Exception'], substr($str, 1));
			break;

		default:
			die('!Unknown order status: ' . substr($str, 0, 1));
	}	
}

function ParseDeliveryType(&$arr, $str)
{
	switch(substr($str, 0, 1))
	{
		case 'R':	// Registered
			ParseDeliveryTypeStatus($arr['Registered'], substr($str, 1));
			break;

		case 'E':	// Express
			ParseDeliveryTypeStatus($arr['Express'], substr($str, 1));
			break;

		case 'B':	// Boxit
			ParseDeliveryTypeStatus($arr['Boxit'], substr($str, 1));
			break;

		case 'M':	// Mail 24h
			ParseDeliveryTypeStatus($arr['Mail24'], substr($str, 1));
			break;

		case 'S':	// Self pickup
			ParseDeliveryTypeStatus($arr['Self'], substr($str, 1));
			break;

		default:
			die('!Unknown delivery type: ' . substr($str, 2, 1));
	}
}

function ParseDeliveryTypeStatus(&$arr, $str)
{
	switch(substr($str, 0, 1))
	{
		case 'W':
			ParseValueType($arr['WaitingApproval'], substr($str, 1));
			break;

		case 'S':
			ParseValueType($arr['Saved'], substr($str, 1));
			break;

		case 'P':
			ParseValueType($arr['Payed'], substr($str, 1));
			break;

		default:
			die('!Unknown delivery type status: ' . substr($str, 0, 1));
	}
}

function ParseValueType(&$arr, $str)
{
	switch(substr($str, 0, 1))
	{
		case 'C':
			$arr['Count'] = ParseValue(substr($str, 1));
			break;

		case 'L':
			$arr['Last'] = ParseValue(substr($str, 1));
			break;

		default:
			die('!Unknown value type: ' . substr($str, 0, 1));
	}
}

function ParseValue($str)
{
	$ret = '';
	
	switch(substr($str, 0, 1))
	{
		case '2':	// 2 characters value (1 byte)
			$ret = (int)substr($str, 1, 2);
			break;

		case '4':	// 4 characters value (2 bytes)
			$ret = (int)substr($str, 1, 4);
			break;

		case '8':	// 8 characters value (4 bytes)
			$ret = (int)substr($str, 1, 8);
			break;

		default:
			die('!Unknown value length: ' . substr($str, 0, 1));
	}
	
	return $ret;
}


?>

זהו זה. עכשיו אוכל לטפל בהזמנות שלכם מהר יותר...
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
סמל אישי של משתמש
Alex
מנהל האתר
הודעות: 763
הצטרף: ה' ינואר 28, 2010 8:27 pm

Re: הרמזור - חיווי מצב ההזמנות

שליחה על ידי Alex »

עדכון למי שעוקב...

מצאתי באג בפונקציה שעושה parsing לנתונים שמגיעים מהשרת.
הנתונים נשלחים בצורת HEX ולעשות להם casting בצורה של (int) זה ממש לא רעיון טוב.
באחד המקרים בהם הצטברו יותר מ-15 הזמנות שהמתינו לטיפול (כלומר 2 ספרות: 0x10), אז משהו התחיל להשתבש (כבר לא זוכר את הפרטים למה).

הגעתי לפונקציה ParseValue שגרמה לבעיות, אז שיניתי אותה וביצעתי את ההמרה ע"י פונקציה hexdec:

קוד: בחירת הכל

function ParseValue($str)
{
	$ret = '';
	
	switch(substr($str, 0, 1))
	{
		case '2':	// 2 characters value (1 byte)
			$ret = (int)hexdec(substr($str, 1, 2));
			break;

		case '4':	// 4 characters value (2 bytes)
			$ret = (int)hexdec(substr($str, 1, 4));
			break;

		case '8':	// 8 characters value (4 bytes)
			$ret = (int)hexdec(substr($str, 1, 8));
			break;

		default:
			die('!Unknown value length: ' . substr($str, 0, 1));
	}
	
	return $ret;
}
עוד משהו שהיה צריך לתקן זה הרגליות של המעמד:
תמונה
הרכבתי את 4 הברגים בטור רגליות כדי להוסיף קצת גובה כי הברגים השטוחים בלטו טיפה והמתקן התנדנד.
עם הברגים הוא לא התנדנד יותר, אבל בגלל שיש לא מעט רעידות מהמדפסת שעל השולחן והרכבים שעוברים בתוך הבניין, הרמזור כל הזמן הסתובב לצד כלשהו, תלוי לאן כבל ה-USB היה מושך אותו. אחרי כמה ניסיונות של לכוון את הכבל ולהעביר אותו בצורה אחרת, נשברתי. פירקתי את הברגים ובמקומם הדבקתי רגליות גומי כאלה:
תמונה

בינתיים עומד יציב ולא זז :D
www.4project.co.il
כל הרכיבים לפרוייקט שלכם
שלח תגובה