Ampelmännchen เป็นสัญลักษณ์ยอดนิยมจากเยอรมนีตะวันออก ซึ่งจะแสดงบนสัญญาณไฟจราจรสำหรับคนเดินเท้าทุกมุมถนน มีแม้กระทั่งเครือข่ายร้านค้าปลีกในเบอร์ลินซึ่งได้รับแรงบันดาลใจจากการออกแบบของพวกเขา
ฉันบังเอิญเจอสัญญาณมือสอง Ampelmann ที่ตลาดนัด และฉันต้องการควบคุมสัญญาณเหล่านั้นจากโทรศัพท์ของฉัน หากคุณต้องการทำเช่นเดียวกัน อ่านต่อ!
คำเตือนด้านสุขภาพ: โปรเจ็กต์นี้ใช้ไฟหลัก 220V ฉันไม่ใช่ช่างไฟฟ้า ปฏิบัติตามคำแนะนำเหล่านี้โดยยอมรับความเสี่ยงเอง
ฉันต้องการสร้างบางสิ่งบางอย่างที่มี สุนทรียศาสตร์แบบเบอร์ลิน และคงจะสนุกสำหรับผู้มาเยือนบ้านของฉันได้โต้ตอบด้วย น่าเสียดายที่จำนวนผู้เยี่ยมชมของเราลดลงอย่างมากในปีนี้ แต่ก็ยังหวังว่าจะได้รับการตอบรับที่ดีในปี 2564...?
อุปกรณ์แต่ละเครื่องในเครือข่ายท้องถิ่นของคุณมีที่อยู่ IP ส่วนตัวของตัวเอง (เช่น 192.168.1.20
) เราเตอร์บางตัว เช่น FritzBox ยังให้คุณเรียกดูตามชื่อโฮสต์ในเครื่องได้ ดังนั้น 192.168.1.20
จึงสามารถเข้าถึงได้ที่ mydevice.fritz.box
สำหรับสัญญาณไฟจราจร ชื่อโฮสต์ของอุปกรณ์คือ traffic-light
เข้าไปดูได้ที่ http://traffic-light.fritz.box
webapp เป็นแอปพลิเคชั่นหน้าเดียวที่ตอบสนองง่ายมาก มันแสดงให้เห็นว่า:
รหัสอยู่ภายใต้ไดเร็กทอรี /webapp ไม่จำเป็นต้องมีการพึ่งพาภายนอก เนื่องจากเป็นเพียงการอาศัยคุณสมบัติมาตรฐานของเบราว์เซอร์ เช่น การแปลง CSS, WebSockets และ XHR คุณสามารถดูตัวอย่างแอปพลิเคชันที่ทำงานอยู่ได้ที่นี่ แม้ว่าจะไม่สามารถควบคุมสิ่งใดๆ ได้ เนื่องจากแน่นอนว่าคุณไม่ได้อยู่บน LAN ของฉัน หากคุณเข้าชมจากเครือข่ายท้องถิ่น เช่น http://traffic-light.fritz.box
มันจะทำงานได้อย่างสมบูรณ์
เมื่อโหลดเพจ แอปพลิเคชันจะส่งคำขอ GET เพื่อค้นหาสถานะปัจจุบันที่ /api/status
จากนั้นเปิดการเชื่อมต่อ WebSocket ไปยังเซิร์ฟเวอร์บนพอร์ต 81
การอัปเดตสถานะครั้งต่อไปจะมาผ่านทาง WebSocket เสมอ เพื่อให้ไคลเอนต์หลายตัวซิงค์กัน แต่ละครั้งที่มีเหตุการณ์ websocket มาถึง เราจะใช้การเปลี่ยนแปลงกับอ็อบเจ็กต์ state
โกลบอลเดียว หลังจากนั้นไม่นาน เมธอด updateScreen()
จะนำการเปลี่ยนแปลงเหล่านั้นไปใช้กับ DOM
เมื่อเริ่มต้นระบบ เรายังตรวจพบด้วยว่าผู้ใช้ใช้อุปกรณ์เคลื่อนที่หรือเดสก์ท็อปเพื่อจัดการกับเหตุการณ์การสัมผัสหรือเหตุการณ์การคลิก จริงๆ แล้วเราใช้เหตุการณ์ touchend
เพื่อส่งคำสั่งไปยังเซิร์ฟเวอร์ เนื่องจากการดำเนินการนี้ทำงานได้อย่างน่าเชื่อถือมากขึ้นบน iPhone X การปัดขึ้นจากด้านล่างของหน้าจอเพื่อออกจาก Safari ถือเป็นการเริ่มเหตุการณ์ touchstart
ทำให้ไม่สามารถออกจากแอปโดยไม่เปิดเครื่องได้ ไฟเขียว!
สุดท้ายนี้ เราต้องการลดภาระงานบนเซิร์ฟเวอร์ทุกครั้งที่เป็นไปได้ โปรดจำไว้ว่า ESP8266 ทำงานบนโปรเซสเซอร์ 80MHz โดยมี RAM เพียงประมาณ 50kB มันไม่ใช่อุปกรณ์อ้วน ดังนั้นเมื่อเบราว์เซอร์ไม่ทำงาน เราจะตัดการเชื่อมต่อเว็บซ็อกเก็ต เมื่อแท็บหรือเบราว์เซอร์เปิดขึ้นมาใหม่ เราจะตรวจสอบสถานะอีกครั้ง และเชื่อมต่อ WebSocket อีกครั้ง
ESP8266 กำลังยุ่งอยู่กับการจัดการคำขอ API และรหัสกำหนดเวลา ดังนั้นจึงไม่มีทรัพยากรที่จำเป็นในการให้บริการเว็บแอป นอกจากนี้ การเปลี่ยนแปลงรูปลักษณ์ของเว็บแอปยังเป็นเรื่องยาก หากฉันต้องเชื่อมต่อกับฮาร์ดแวร์ทุกครั้งที่ต้องการใช้การอัปเดต
index.html ของ webapp เป็นไปตามหลักการแอปพลิเคชันหน้าเดียวที่ Javascript ควรแสดงผลทุกอย่าง ซึ่งทำให้เนื้อหา HTML มีขนาดเล็กมาก ขนาดเล็กประมาณ 550 ไบต์ เบราว์เซอร์ของไคลเอ็นต์จะโหลดอย่างอื่นทั้งหมดโดยไม่จำเป็นต้องเรียกเซิร์ฟเวอร์เพิ่มเติม ดังนั้น webapp จึงถูกโฮสต์ทั้งหมดบน GitHub Pages ซึ่งเป็นเครื่องมือโฮสต์ไซต์แบบคงที่ฟรี การกดปุ่ม /index.html
จะส่งคำขอพร็อกซีไปยังหน้า GitHub และส่งคืนผลลัพธ์ไปยังเบราว์เซอร์ไคลเอ็นต์
ตอนนี้เราสามารถเปลี่ยนแปลงอะไรก็ได้ในเว็บแอป และเซิร์ฟเวอร์จะไม่ได้รับผลกระทบ ยอดเยี่ยม! เกือบแล้ว...
โค้ดส่วนใหญ่สำหรับเว็บแอปนี้อยู่ในไฟล์ CSS และ JS ไม่ใช่ใน index.html
เบราว์เซอร์จะแคชไฟล์ที่โหลดมาเป็นระยะเวลาไม่แน่นอนก่อนที่จะส่งคำขออีกครั้ง หาก index.html ไม่เปลี่ยนแปลง แต่เราได้ปรับใช้ JS เวอร์ชันใหม่แล้ว ลูกค้าของเราจะรู้ได้อย่างไรว่าพวกเขาจำเป็นต้องโหลด JS เวอร์ชันใหม่
เมื่อเราพุชโค้ดเวอร์ชันใหม่ของเราไปที่สาขา git master
GitHub Action จะทำงาน ซึ่งจะดำเนินการปรับใช้กับเพจ GitHub ซึ่งเป็นที่ที่เพจให้บริการแก่สาธารณะจริงๆ เคล็ดลับคือการเติมส่วนต่อท้าย ?version=latest
ต่อท้ายไฟล์ CSS และ JS ของเราใน index.html
ก่อนที่จะคัดลอกเนื้อหาไปยังสาขา gh-pages
การดำเนินการจะใช้คำสั่ง sed
เพื่อแทนที่ " latest
" ด้วยค่าของตัวแปร $GITHUB_SHA
ซึ่งจริงๆ แล้วเป็นรหัสคอมมิตสุดท้ายในสาขา master
(เช่นค่าเช่น b43200422c4f5da6dd70676456737e5af46cb825
)
ครั้งถัดไปที่ลูกค้าเข้าชมเว็บแอป เบราว์เซอร์จะเห็นค่าใหม่ที่แตกต่างหลังจาก ?version=
และขอไฟล์ JS หรือ CSS ใหม่ที่ได้รับการอัปเดต ซึ่งจะยังไม่ได้แคชไว้
ดูวิธี setup(void)
ใน traffic-light-controller.ino
และส่วนรหัส Arduino สำหรับวิธีการทำงานในทางปฏิบัติ
ฉันตัดสินใจใช้ทั้ง REST และ WebSockets ควบคู่กัน REST ส่วนใหญ่จะใช้โดยไคลเอนต์เพื่อควบคุมเซิร์ฟเวอร์ WebSockets ใช้เพื่อเผยแพร่ข้อมูลสถานะไปยังไคลเอนต์ มีเครื่องมือมากมายเช่น Postman ที่ให้คุณทดลองใช้ REST API ได้อย่างง่ายดาย ดังนั้นฉันจึงพบว่าสะดวกกว่า
HTTP API: โปรดดูเอกสารประกอบ Swagger ที่นี่
WebSocket API: การเชื่อมต่อ websocket ส่ง JSON blobs ซึ่ง webapp ใช้เพื่ออัปเดตสถานะภายใน เหตุการณ์ websocket สามารถมีช่องที่ต้องอัปเดตอย่างน้อย 1 ช่อง ตัวอย่างที่มีข้อมูลด้านสิ่งแวดล้อมอาจมีลักษณะดังนี้:
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
ปัจจุบันไม่มีการส่งข้อมูลจากไคลเอนต์ไปยังเซิร์ฟเวอร์ผ่านทาง websocket แม้ว่าจะเป็นไปได้ก็ตาม
รหัส Arduino ทั้งหมดอยู่ในไฟล์เดียวที่มีความคิดเห็นที่อธิบาย
โดยเริ่มต้นด้วยชุดคำจำกัดความสำหรับตำแหน่งพิน การนำเข้าไลบรารี และค่าฮาร์ดโค้ดสำหรับสิ่งต่างๆ เช่น ประเภทเนื้อหา HTTP และค่าโค้ดตอบกลับ ต่อไปนี้คือชุดของตัวแปรที่สามารถเปลี่ยนแปลงได้ขณะรันไทม์ โดยทั้งหมดจะมีเครื่องหมายขีดล่างนำหน้า นอกจากนี้ยังมีออบเจ็กต์บางส่วนที่เริ่มต้นใช้งานที่นี่ รวมถึงออบเจ็กต์สำหรับเว็บเซิร์ฟเวอร์ เว็บเซิร์ฟเวอร์ซ็อกเก็ต ไคลเอนต์ WiFi และเซ็นเซอร์อุณหภูมิ "นาฬิการะบบ" ได้รับการดูแลโดยฟิลด์ _currentMillis
หลังจากบู๊ตแล้ว เมธอด setup(void)
จะทำงาน หลังจากดำเนินการตั้งค่าพินแล้ว ระบบจะสร้างการแมปที่จำเป็นสำหรับตำแหน่งข้อมูล REST และเริ่มเซิร์ฟเวอร์ที่รับฟังคำขอของไคลเอ็นต์ เมธอด loop(void)
มีหน้าที่รับผิดชอบทุกอย่าง แต่ละรอบจะประมวลผลคำขอเว็บที่ค้างอยู่ อัปเดตรอบจังหวะ และอ่านเซ็นเซอร์หากจำเป็น หากเราอยู่ในโหมดปาร์ตี้ มันจะตั้งค่าสถานะแฟลช/พัลส์ปัจจุบัน
จังหวะ (สำหรับโหมดปาร์ตี้) ได้รับการฮาร์ดโค้ดเพื่อเล่นซีเควนซ์ในฟิลด์ RHYTHM_PATTERN
แต่ตามทฤษฎีแล้ว สามารถเปลี่ยนเป็นอย่างอื่นในขณะรันไทม์ได้ ทุกครั้งที่เราเรียกใช้เมธอด rhythm()
เราจะใช้ค่า _bpm
และ _currentMillis
ปัจจุบันเพื่อพิจารณาว่าเราควรอยู่ในตำแหน่งใดในรูปแบบ ข้อมูลนี้ถูกจัดเก็บไว้ในฟิลด์ _rhythmStep
ในระหว่างรูปแบบจังหวะ จะมีช่วงที่รีเลย์ทั้งสองตัวถูกปิดจริง แต่เนื่องจากไฟเป็นแบบหลอดไส้ จึงไม่สตาร์ทหรือหยุดเปล่งแสงทันที ดูเหมือนว่าหลอดไฟจะใช้เวลาประมาณ 1.7 วินาทีในการเปิดหรือปิดโดยสมบูรณ์ ดังนั้นโดยการเพิ่มระยะเวลาภายในรูปแบบที่ทั้งสองปิดอยู่ เราจะได้รูปแบบการเต้นที่นุ่มนวลในขณะที่หลอดไฟอุ่นขึ้นและเย็นลง
ในเมธอด partyFlash()
เราได้รับรายการรูปแบบที่ควรจะแสดงในปัจจุบัน (หรือทั้งสองอย่างจะถูกปิด) และเรียก lightSwitch(...)
ด้วยพารามิเตอร์ที่เหมาะสม lightSwitch(...)
ในทางกลับกันจะเรียก sendToWebSocketClients(...)
เพื่อให้ไคลเอ็นต์ที่เชื่อมต่อทั้งหมดได้รับการอัปเดตเป็นสถานะใหม่
หากผู้ใช้เพียงคลิกที่ไฟดวงใดดวงหนึ่งเพื่อเปิดหรือปิด กระบวนการจะคล้ายกัน แต่ได้รับการจัดการเป็นคำขอ REST หนึ่งในเมธอด handleX
ถูกเรียก ซึ่งจะตรวจสอบคำขอ และในทางกลับกันจะเรียก lightSwitch(...)
ในช่วงเวลาไม่บ่อยนัก เราจะตรวจสอบอุณหภูมิของตู้ทั้งสองเครื่อง และยังส่งข้อมูลนี้ผ่าน WebSocket ไปยังไคลเอนต์ทั้งหมดด้วย ขณะนี้ใช้เพื่อวัตถุประสงค์ในการให้ข้อมูลเท่านั้น แต่สามารถใช้เพื่อปิดไฟเมื่ออุณหภูมิเกินขีดจำกัดความปลอดภัยบางประการ
ขอมอบเครดิตให้กับ @mrcosta สำหรับความช่วยเหลือของเขาในการทบทวนบทความนี้