חיפוש
  • יניב אור

פרויקט Wikey - פיתוח התוכנה ל-ESP8266 - בארדואינו

עודכן ב: יול 2

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


הקוד מקור נמצא כאן: https://github.com/YanivOr/wikey


חלק זה הוא המשך למאמר הבא: פרויקט Wikey - קודן ללא מגע יד אדם - הקדמה


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

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


הקובץ הראשי נקרא wikey והוא מכיל את כל הקריאות לספריות השונות, הגדרות מאקרו, הגדרות קבועים גלובאליים, הגדרות משתנים גלובאליים נוספים, את פונקציית ה-setup הראשית ואת פונקציית ה-loop הראשית. בשתי הפונקציות - setup ו-loop - לרוב לא אכתוב לוגיקה אלא קריאה לפונקציות שנמצאות בקבצים אחרים.


שאר הקבצים מטפלים בחלקים השונים של התוכנה ומכילים פונקציות שונות בהתאם. אם יש צורך שפונקציונליות מחלק מסויים תופיע ב-setup או ב-loop הראשיות - אכתוב פונקציה שמטפלת אך ורק בעניין הזה כשהשם של הפונקציה יתחיל באחת משתי המילים setup או loop. ניקח לדוגמא את הקובץ webServer, שמטרתו היא פתיחת שרת web על הרכיב. הוא מכיל, בין היתר, את שתי הפונקציות: setupWebServer ו-loopWebServer. כאמור, קריאה אליהן תתבצע בקובץ הראשי באחת משתי הפונקציות, בהתאם.


ניפוי שגיאות וכתיבת לוגים

לצורך ניפוי שגיאות (debugging) אני משתמש בפונקציות ששייכות לספריית Serial וב-Serial Monitor להצגת הפלט. החלטתי לא השתמש ב-Serial באופן ישיר אלא לעטוף אותו בפונקציות. כך אוכל להחליף בקלות, אם ארצה בכך בהמשך, את ספריית ה-Serial הזאת. זאת סיבה אחת למעטפת. סיבה נוספת היא האפשרות לאפשר או לא לאפשר את הצגת הפלט בעזרת משתנה בוליאני אחד בודד. הקובץ נקרא debugger והוא מכיל שלוש פונקציות. הראשונה היא setupDebugger אשר מאתחלת את Serial ומגדירה את ה-baud rate הרצוי. הפונקציה השניה היא debug אשר מקבלת כפרמטר msg מסוג String ובסך הכל מריצה את Serial.print. הפונקציה השלישית היא debugln, דומה מאוד ל-debug רק שהיא מריצה את Serial.println כך שבסיום ההדפסה הסמן ירד לשורה חדשה. שתי הפונקציות הללו רצות בתנאי שהמשתנה הבוליאני הגלובלי debugger הוא true.


הרכיב כ-Access Point

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


נעבור לקובץ הראשי של הפרויקט, הקובץ שנקרא wikey. שימו לב לקבועים ssid ו-password שמוגדרים כ-wikey ו-11111111 בהתאמה. אלו הם שם הרשת והסיסמא שהזכרתי קודם לכן.


נמשיך לפונקציה setup. דבר ראשון, אני מגדיר את הרכיב כך שיהיה בשני מצבי WiFi. גם כ-Access Point וגם כ-Station. זה נעשה בעזרת הפונקציה WiFi.mode שמקבלת כפרמטר את WIFI_AP_STA. פונקציה נוספת לה אני קורא ב-setup היא setupAccessPoint אשר מוגדרת בקובץ AccessPoint. כל מה שהיא עושה זה להגדיר את הרכיב כ-AP בעזרת WiFi.softAP אשר מקבלת כפרמטרים את שם הרשת והסיסמא. כתובת ה-IP שתוגדר ואותה אני מדפיס בלוג לרוב תהיה 192.168.4.1. זאת הכתובת אותה הטכנאי יזין בדפדפן כדי לקנפג את הרכיב. כדי שלמשתמש יהיה נוח יותר, אני משתמש בספריה mDNS שיוצרת שרת dns מקומי כך שבמקום לכתוב כתובת IP, תהיה אפשרות להשתמש בשם דומיין. שרת ה-dns המקומי מאותחל בעזרת הפונקציה begin עם המחרוזת esp8266 כך שהכתובת אותה נכתוב בדפדפן תהיה: http://esp8266.local. אגב, הספריה mDNS איננה נתמכת בכרום לאנדרואיד.


לאחר קינפוג הרכיב בו נעשית בחירת רשתות אלחוטיות אליהן תהיה לו אפשרות להתחבר (על כך בהמשך) ולאחר שהרכיב יתחבר לאחת הרשתות שהוגדרו - אני מנתק את מצב ה-Access Point. זה נעשה בקובץ station עם הפונקציה: WiFi.softAPdisconnect שמקבלת כפרמטר true.


במידה ויש צורך בהמשך לחזור ולקנפג מחדש את הרכיב, לטכנאי תהיה אפשרות לעשות זאת אך ורק בלחיצה פיזית ממושכת על אחד הכפתורים. כרגע, זה נעשה בלחיצה על הכפתור Flash, שהוא אחד משני הכפתורים בערכת הפיתוח NodeMCU ESP-12E ומחובר ל-GPIO מספר 0. הקובץ שמטפל בפעולת הכפתור נקרא flashButton ובו מתבצעת קריאה לפונקציה setupAccessPoint ברגע שיש קריאה ל-onLongPressed (בלחיצה ממשוכת על הכפתור, כפי שציינתי).


הרכיב כשרת web

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


בקובץ הראשי אני מגדיר server בעזרת ESP8266WebServer, כך שיאזין לפורט 80. הפונקציונליות והלוגיקה שקשורות בשרת ה-web נמצאות בקובץ webServer. בהמשך אעבור על הפונקציות השונות. לפני כן נתעכב טיפה על התוכן אותו מחזיר השרת ללקוח (הדפדפן) - קבצי HTML, CSS, Javascript וכו'. יש אפשרות להחזיר את התוכן כמחרוזות ואולי זה יכול להיות ריאלי אם מדובר בדף אינטרנט פשוט ביותר וללא תוכן רב אך כשמדובר באתר שלם בעל מספר דפים ותוכן רב - זה מאוד לא נוח לעבוד בצורה כזאת ומאוד קשה לתחזוקה. זאת לא הדרך הנכונה. ב-ESP8266 וברכיבים נוספים ישנו פיצ'ר שנקרא SPIFFS - קיצור של SPI Flash File System. הפיצ'ר הזה מאפשר שימוש ב-flash כמערכת קבצים אליה ניתן לכתוב ולקרוא קבצים בצורה נוחה. לצורך שימוש ב-SPIFFS בסביבת הארדואינו, יש צורך לפתוח תיקיית data בתוך תיקיית הפרויקט. את הקבצים מעלים לרכיב דרך הממשק. נכנסים ל-Tools ואז ל-ESP8266 Sketch Data Upload.


נחזור לקובץ webServer. נתחיל בפונקציה setupWebServer - שאליה אני מבצע קריאה ב-setup הראשי. בפונקציה הזאת אני מאתחל את השרת ואת SPIFFS. כמו כן, אני פותח האזנה ל-event-ים של בקשות לשלושה קבצים: ל-root, ל-app.js ול-style.css. ברגע שיש בקשה לאחד הקבצים הללו, מתבצעת קריאה לפונקציה המתאימה שמובילה לקריאה ל-handleFileRead שמקבלת כפרמטר את שם הקובץ. ניתן לראות שקריאה ל-root (/) מתפרשת כקריאה לקובץ index.html. הקבצים נטענים מה-flash באמצעות SPIFFS ומוחזרים לדפדפן כתשובה לבקשה. בנוסף לתוכן הקובץ, מועבר גם סוג הקובץ אותו אני אוסף באמצעות הפונקציה getContentType.


פונקציה נוספת ב-webServer היא הפונקציית loop שנקראת loopWebServer ואליה מתבצעת קריאה מה-loop הראשי. במידה והמשתנה הבוליאני webServerEnabled הוא true, יש האזנה לבקשות מלקוחות.


דפי ה-Web

כפי שציינתי קודם לכן, דפי ה-web נמצאים בפרויקט בתיקיית data שממנה נעשית העלאה ל-flash בעזרת הפיצ'ר SPIFFS. כאמור, מדובר על שלושה קבצים בלבד. הקובץ index.html, הקובץ style.css והקובץ app.js. אני לא עושה שימוש בספריות או בפריימוורקים וכל הקוד כתוב ב-vanilla js. רוב התקשורת בין הדפדפן לשרת נעשית באמצעות WebSocket כשהקוד שמטפל בכך נמצא בקובץ webSocketServer. נגיע אליו בהמשך.



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


הרכיב כשרת websocket

בקובץ הראשי - wikey - יש יצירת אובייקט WebSocketsServer עם הערך של פורט 81 אליו יאזין. הפונקציונליות של התקשורת בין הרכיב לדפי ה-web המקומיים באמצעות websocket נמצאת בקובץ webSocketServer. הפונקציה setupWebSocketServer נקראת ע"י ה-setup הראשי ויש בה אתחול של האובייקט webSocketServer ותחילת האזנה לפורט 81. כמו כן יש האזנה ל-event-ים שעתידים להתרחש.


האירוע WStype_DISCONNECTED מוביל אך ורק להדפסת לוג שמציין על הניתוק שהתבצע. גם באירוע WStype_BIN אין כרגע ביצוע של פעולה כלשהי.


באירוע WStype_CONNECTED נעשית הבדיקה הבאה. אם המספר הסידורי נקרא מה-EEPROM והתבצע חיבור לרשת - כלומר הקינפוג התבצע כבר בעבר - יש שליחת הודעה מסוג CONNECT לדפדפן עם ה-status של connected. אחרת, יש שליחת הודעה מסוג INIT כשהמידע שנשלח הוא המספר הסידורי אותו הגריל הרכיב.


האירוע WStype_TEXT מטפל בבקשות שונות שמגיעות מהדפדפן. הראשונה SCAN, בקשה לסריקת רשתות והחזרת הרשימה המעודכנת. השניה CONNECT, אליה מתוספים הנתונים לחיבור לרשתות, אותם הזין הטכנאי. הבקשה השלישית היא בקשה ל-RESET שקוראת בהמשך ל-resetStation. הפונקציה הזאת מנקה את ה-EEPROM ומאתחלת את הרכיב.


הרכיב כתחנה - station

הקובץ שמטפל בפונקציונליות שקשורה בחיבור לרשתות אלחוטיות הוא station. הפונקציה הראשית שאחראית להתחברות היא setStation והיא מקבלת כפרמטרים ssid ו-password. אני נותן לתהליך החיבור זמן קצוב של 10 שניות ומשתמש ב-delay לשם כך. אמנם delay גורמת לבלוקינג של המערכת ונהוג לא להשתמש בה, אבל במקרה הזה השימוש בה הוא תקין מבחינתי. אם הרכיב לא הצליח להתחבר, אני כותב על כך ללוג. אם ההתחברות הצליחה, אני מנתק את ה-AP ולא מאפשר לשרת web לשרת לקוחות נוספים. בנוסף לכך, אני יוצר חיבור לשרת המרוחק שנמצא בענן - חיבור דרך websocket. נגיע לחלק הזה בהמשך.


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


הרכיב כלקוח websocket

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


הפונקציה שמטפלת באירועים היא webSocketClientEvent. האירוע WStype_TEXT, דרכו מועברות פקודות מהשרת המרוחק ע"י המפעיל. בהמשך נכיר גם את החלק הזה שמורכב משרת NodeJS ודפי web אותם משרת Nginx. הפקודה GPIO אומרת לרכיב להדליק או לכבות GPIO ספציפי. הפקודה PULSE אומרת לרכיב להדליק ולכבות GPIO ספציפי בתדירות מסוימת וכמות מסוימת של פולסים. הפקודה STR מדפיסה ללוג את המחרוזת שנשלחה.


טיפול ב-GPIO

כהמשך לחלק הקודם, נעבור לקובץ gpio ונראה איך הפקודות GPIO ו-PULSE מתבצעות. הפונקציה שמטפלת בפקודה GPIO היא onOffGpio שמקבלת כפרמטרים מספר פין ואחד משני הערכים 0 או 1 האם להדליק או לכבות. פוונקציה פשוטה שמשתמשת ב-pinMode וב-digitalWrite.


לצורך ביצוע הפקודה PULSE אני מריץ את loopPulseGpio בפונקצית loop הראשית ומאפשר את הביצוע שלה ע"י המשתנה הבוליאני pulseEnabled. יש קריאה לפונקציה בכל loop מכיוון שאני עושה שימוש ב-millis כדי לדגום ולהשוות טווח של זמן ברזולוציה של אלפיות שנייה וכך ליצור timeline. הפונקציה setPulse מקבלת מספר פין, את startAs שמציין האם להתחיל את הפולס גבוה (1 - דלוק) או נמוך (0 - כבוי), מקבלת את התדירות בהרץ ואת כמות הפולסים. כמו כן היא מבצעת השמה של הערך true למשתנה הבוליאני pulseEnabled וכך גורמת ל-loopPulseGpio להתבצע.


יצירת המספר הרנדומלי

הקובץ שמטפל ביצירת המספר הרנדומלי שמופיע בדף web הראשי של הקונפיגורציה ואיתו מזדהה הרכיב נקרא uid והוא מכיל שתי פונקציות פשוטות. האם בשימוש בפונקציה random לטווח בין 1111111111 עד 9999999999. זאת אפשרות אחת. אפשרות אחרת היא להגריל 10 ספרות ולחבר אותן יחד (concatenate) כמחרוזת. בחרתי באחרונה. כל ספרה היא בין 1 ל-9 (כולל), ללא הספרה 0. אבדוק בהמשך מה הדרך האופטימלית.


שימוש ב-EEPROM

ה-EEPROM במיקרוקונטרולרים משמש לשמירת מידע שלא נמחק בעת כיבוי הרכיב. בחרתי לשמור בו את ה-uid ואת פרטי החיבור לרשתות האלחוטיות. הקובץ המטפל בשמירת הנתונים נקרא eeprom. את המידע אני שומר כ-JSON ושולף אותו בצורה deserialized אל תוך doc.


כפתור להחזרת הרכיב למצב קינפוג

במצבים מסויימים, כמו למשל התקנה מחדש של הרכיב, החלפת סיסמא ברשת האלחוטית או שינוי הרשת האלחוטית - יהיה צורך לקנפג מחדש את הרכיב. כפי שציינתי קודם לכן, לאחר התחברות לרשת אני מכבה את ה-AP ואת פעולת השרת המקומי שעל הרכיב. הדרך להחזיר את הרכיב למצב קינפוג, כלומר להדליק את ה-AP ואת השרת היא ע"י לחיצה על כפתור פיזי שמחובר לרכיב. לחיצה ממושכת. בחרתי, לצורך העניין, את כפתור ה-flash שנמצא בערכת הפיתוח ESP-12 ומחובר ל-GPIO מספר 0. אני משתמש בספריה EasyButton כדי להימנע מלהמציא את הגלגל בכל הנוגע ל-debouncing ובכל הנוגע ללחיצות מסוגים שונים - לחיצה קצרה או ארוכה. ספריה מאוד נוחה וקלה לשימוש. הקובץ שמטפל בעניין נקרא flashButton. שם שלאו דווקא מייצג את הפעולה שלו ובחרתי בו משום שהוא נקרא flash בערכת הפיתוח הספציפית. אולי אשנה בהמשך, אולי לא. זה לא ממש מפריע.


זאת ערכת הפיתוח בה אני משתמש. ESP-12. שימו לב לכפתור ה-FLASH.


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


בהמשך אוסיף תמונות וסרטונים נוספים.


נעבור לחלק הבא שהוא השרת המרוחק שיושב בענן ומשמש לשליטה מרחוק על הרכיב.


פרטים נוספים בקרוב...

© 2023 by DO IT YOURSELF. Proudly created with Wix.com