Thread เป็นโปรโตคอลสื่อสารไร้สายระหว่างอุปกรณ์ที่นิยมใช้กันมากในอุปกรณ์ระบบอัตโนมัติภายในบ้าน (Home Automation) มีข้อดีประการสำคัญคือทำงานแบบ Mesh network และเป็นระบบแบบกระจายศูนย์ (Decentralized System) เมื่ออุปกรณ์ตัวใดตัวหนึ่งดับไป จะไม่ส่งผลต่อการสื่อสารระหว่างอุปกรณ์อื่น ๆ ทำงานอยู่บนมาตรฐาน IEEE 802.15.4 คลื่นความถี่ 2.4G ใช้งานได้โดยไม่ต้องขออนุญาตทำ นำเข้า และใช้งาน
หมายเหตุ. แม้ Thread จะทำงานอยู่บนคลื่น 2.4G เหมือน WiFi และชื่อมาตรฐานคล้ายกัน แต่ไม่สามารถเชื่อมต่อ WiFi ได้ (เป็นคนละมาตรฐานกัน)
Thread ทำงานในระดับการจัดการเชื่อมต่อระหว่างอุปกรณ์ ครอบคลุมตั้งแต่การร้องขอ-ยอมรับการเชื่อมต่อจากอุปกรณ์อื่น, การทำหน้าที่ส่งต่อข้อมูลให้อุปกรณ์อื่นในเครือข่าย (Router), การทำหน้าที่แจกหมายเลขประจำตัว (ID) ให้แต่ละอุปกรณ์ในเครือข่าย
การใช้งาน Thread เปรียบเสมือนกับการใช้งานโปรโตคอล TCP/UDP ในโลกอินเทอร์เน็ต คือ Thread ทำหน้าที่ส่งข้อมูลไปที่ปลายทางตามที่สั่งเท่านั้น ส่วนรูปแบบข้อมูลที่รับ-ส่งกัน ขึ้นอยู่กับผู้ออกแบบเป็นผู้กำหนด นั่นหมายความว่าอุปกรณ์ Thread แต่ละยี่ห้อที่จำหน่ายในท้องตลาด อาจไม่สามารถติดต่อสื่อสารกันได้โดยตรงแม้จะเป็น Thread เหมือนกัน เพราะผู้ผลิตแต่ละเจ้ามีการกำหนดรูปแบบข้อมูลจริง ๆ ไม่เหมือนกัน
แบ่งตามบทบาทการส่งต่อข้อมูลได้ดังนี้
ทำหน้าที่รับและส่งต่อข้อมูลไปยังอุปกรณ์ปลายทาง (End Device) รวมทั้งยอมรับ/ไม่ยอมรับการเข้าร่วมเครือข่ายของอุปกรณ์
เป็นอุปกรณ์ใน Node สุดท้าย ทำหน้าที่รับข้อมูลเพียงอย่างเดียว เช่น หลอดไฟ ทำหน้าที่รับคำสั่งเปิด-ปิดไฟ, สวิตช์ ทำหน้าที่รับการกดปุ่มจากผู้ใช้แล้วส่งข้อมูลผ่านเครือข่ายไปยังอุปกรณ์ปลายทาง เป็นต้น
ด้วยความสามารถของ Mesh network อุปกรณ์ปลายทางอาจเปลี่ยนตัวเองเป็นเร้าเตอร์ก็ได้ (ในกรณีที่บริเวณนั้นไม่มีเร้าเตอร์ตัวอื่น ๆ อยู่เลย เพื่อให้อุปกรณ์ในเครือข่ายยังสามารถสื่อสารกันได้ครบถ้วน) เช่น หลอดไฟดวงที่ 1 เชื่อมต่อกับเร้าเตอร์ A แต่หลอดไฟดวงที่ 2 อยู่ไกลจากเร้าเตอร์ A มาก หลอดไฟดวงที่ 1 จึงทำหน้าที่เป็นเร้าเตอร์ให้หลอดไฟดวงที่ 2 แทน
อุปกรณ์ปลายทาง 1 ตัว สามารถเชื่อมต่อกับเร้าเตอร์ได้เพียง 1 ตัวเท่านั้น กรณีเร้าเตอร์ที่เชื่อมต่ออยู่ไม่สามารถทำหน้าที่ได้อีกต่อไป จะมีการเชื่อมต่อไปยังเร้าเตอร์ตัวอื่นอัตโนมัติ
ทำงานแบบเดียวกับเร้าเตอร์ แต่มีหน้าที่พิเศษที่คอยจัดการ ID ของเร้าเตอร์ตัวอื่นในเครือข่าย โดย Thread Leader มักเป็นอุปกรณ์ที่เปิดขึ้นมาตัวแรกสุดในระบบเครือข่าย
หลักการทำงาน คือ เริ่มต้นอุปกรณ์จะพยามขอเข้าเชื่อมต่อและขอ ID จากเครือข่าย หากไม่ได้รับการตอบกลับใด ๆ เลย อุปกรณ์จะเปลี่ยนตัวเองเป็น Thread Leader เพื่อให้เร้าเตอร์ / อุปกรณ์ปลายทางที่เปิดขึ้นมาทีหลังให้เข้ามาเชื่อมต่อแทน
หาก Thread Leader ในระบบเครือข่ายไม่สามารถทำหน้าที่ได้ จะมีเร้าเตอร์ตัวอื่นเข้ามาทำหน้าที่เป็น Thread Leader แทนโดยอัตโนมัติ เพื่อให้การรับ-ส่งข้อมูล และการกำหนด ID ในเครือข่ายยังทำงานได้ปกติ
เป็นเร้าเตอร์ที่เพิ่มความสามารถเชื่อมต่อกับโลกอินเทอร์เน็ตผ่าน WiFi หรือพอร์ต LAN ทำให้อุปกรณ์ในเครือข่าย Thread สามารถติดต่อกับโลกอินเตอร์เน็ตได้
ตัวอย่าง หากต้องการสั่งงานหลอดไฟในเครือข่าย Thread ผ่านแอปพลิเคชันในโทรศัพท์มือถือ ในเครือข่าย Thread จะต้องมี Border Router อยู่ด้วย เพื่อให้แอพฯ ส่งข้อมูลผ่านอินเตอร์เน็ตเข้ามาที่ Border Router แล้วส่งข้อมูลผ่านเร้าเตอร์ตัวอื่น ๆ ไปยังหลอดไฟที่เป็นอุปกรณ์ปลายทางได้
รายละเอียดชนิดอุปกรณ์ฉบับเต็ม อ่านได้ที่บทความ Node Roles and Types
หมายเลขของอุปกรณ์ในเครือข่ายถูกกำหนดตามมาตรฐาน IPv6 โดยหมายเลขของแต่ละอุปกรณ์จริง ๆ จะมีเพียง 3 ตัวท้ายเท่านั้น ซึ่ง 3 ตัวท้ายนี้เรียกว่า RLOC16 โดยจะแบ่งเป็น 1) ตัวหน้าสุดบอกหมายเลข ID ของเร้าเตอร์ และ 2) 2 ตัวสุดท้ายบอก ID ของอุปกรณ์
จากรูปจะเห็นว่าเร้าเตอร์ A มีหมายเลข RLOC16 เป็น 0x400 แล้วอุปกรณ์ปลายทางที่อยู่ภายใต้เร้าเตอร์ A มีหมายเลข RLOC16 เป็น 0x401, 0x402, ... ไปเรื่อย ๆ ส่วนหมายเลขอุปกรณ์เต็ม ๆ คือ fde5:8dba:82e1:1:1::ff:fe00:401 และ fde5:8dba:82e1:1:1::ff:fe00:402 (สังเกตว่าตัวอักษรชุดหน้าจะเหมือนกัน มีเพียง 3 ตัวท้ายที่เปลี่ยน)
รายละเอียดหมายเลข IP อ่านได้ที่บทความ IPv6 Addressing
ESP32 ที่ใช้งาน Thread ได้ จะต้องรองรับการเชื่อมต่อตามมาตรฐาน IEEE 802.15.4 เท่านั้น ซึ่งในขณะที่เขียนบทความอยู่ มี ESP32-C6 และ ESP32-H2 ที่รองรับมาตรฐานดังกล่าว
การเขียนโปรแกรมสั่งงาน ESP32-C6 และ ESP32-H2 เพื่อใช้งาน Thread รองรับการเขียนผ่าน ESP-IDF และ Arduino Core for ESP32 ส่วนภาษาอื่น ๆ เช่น MicroPython, Lua ยังไม่มีนักพัฒนาที่ทำรองรับ (ณ วันที่ทำบทความนี้)
ไลบรารี่ที่ใช้คือ OpenThread ฝังมาใน ESP-IDF และ Arduino Core for ESP32 เรียบร้อยแล้ว (ไม่ต้องติดตั้งเพิ่ม)
ไลบรารี่ OpenThread รองรับการสั่งงานผ่าน CLI (Command Line Interface) โดยเมื่อเข้าใจหลักการทำงานของ Thread แล้ว สามารถย้ายไปใช้ API ในภาษา C/C++ เพื่อสั่งงานได้
บทความนี้ใช้ Arduino Core for ESP32 บน Arduino IDE เนื่องจากมีโค้ดตัวอย่างที่ทำความเข้าใจได้ง่ายอยู่แล้ว (ง่ายกว่า ESP-IDF มาก)
Arduino Core for ESP32 ที่รองรับ Thread มีในเวอร์ชั่น 3 ขึ้นไป หากยังไม่เคยติดตั้ง Arduino Core for ESP32 มาก่อน แนะนำให้ทำตามบทความ ... ก่อน แต่หากติดตั้งไว้แล้ว ให้ตรวจสอบเวอร์ชั่นของ Arduino Core for ESP32 ตามขั้นตอนดังนี้
ที่โปรแกรม Arduino IDE ให้กด Boards Manager แล้วพิมพ์ค้นหา ESP32 จากนั้นสังเกตเวอร์ชั่น จะต้องเป็น 3 ขึ้นไปเท่านั้น (หากยังเป็น 2.x.x อยู่ ให้กดปุ่ม UPDATE ให้เป็นเวอร์ชั่นล่าสุด)
ผู้เขียนเลือกใช้บอร์ด ESP32-C6 เป็นอุปกรณ์ปลายทาง (End Device) และใช้ ESP32-H2 เป็น Thread Leader ทดสอบสื่อสารกันผ่าน Thread
ที่โปรแกรม Arduino IDE ให้เลือกบอร์ดเป็น ESP32-C6 (หากไม่เลือกบอร์ดก่อน จะไม่มีเมนูเปิดโค้ดตัวอย่างแสดงขึ้นมา) เปิดโปรแกรมตัวอย่าง SimpleCLI โดยกด File > Examples > OpenThread > SimpleCLI
เลือกพอร์ตให้ถูกต้อง จากนั้นอัพโหลดโปรแกรมลงบอร์ด ESP32-C6
เปิดโปรแกรม Arduino IDE เพิ่มอีก 1 หน้าต่าง เลือกบอร์ดเป็น ESP32-H2 เปิดโปรแกรมตัวอย่าง SimpleCLI แล้วอัพโหลดโค้ดโปรแกรมลงบอร์ด (ขั้นตอนเดียวกันกับ ESP32-C6)
ใช้โปรแกรม PuTTY หรือ Xshell (ผู้เขียนเลือกใช้ Xshell) เชื่อมต่อไปที่พอร์ตของ ESP32-C6 และ ESP-H2 พร้อมกัน แล้วกดปุ่ม Reset บนบอร์ด ได้ Terminal ที่พร้อมพิมพ์คำสั่ง (ดังรูป)
พิมพ์คำสั่งเข้าไปทั้ง 2 ฝั่งดังนี้
# สร้างการตั้งค่าใหม่
dataset init new
# ดูการตั้งค่าทั้งหมด
dataset
การตั้งค่าทั้งหมดจะแสดงขึ้นมา (ดังรูป)
การตั้งค่า จะยึด Leader (ESP32-H2) เป็นหลัก สังเกตว่าตอนนี้ค่า Channel, Ext PAN ID, Mesh Local Prefix, Network Key, Network Name, PAN ID ของ ESP32-C6 ไม่ตรงกับของ ESP32-H2 ซึ่งค่าเหล่านี้ต้องตั้งให้ตรงกัน จึงจะเชื่อมต่อกันได้
ใช้คำสั่งดังนี้ที่ฝั่ง ESP32-C6 เพื่อกำหนดค่า Channel, Ext PAN ID, Mesh Local Prefix, Network Key, Network Name, PAN ID ให้ตรงกับ ESP32-H2
# กำหนดค่า Network Key
dataset networkkey <Network Key ของ ESP32-H2>
# กำหนดค่า Network Name dataset networkname <Network Name ของ ESP32-H2>
# กำหนดค่า PAN ID dataset panid <PAN ID ของ ESP32-H2>
# กำหนดค่า Ext PAN ID dataset extpanid <Ext PAN ID ของ ESP32-H2>
# กำหนดค่า Mesh Local Prefix dataset meshlocalprefix <Mesh Local Prefix ของ ESP32-H2>
# กำหนดค่า Channel dataset channel <Channel ของ ESP32-H2>
จากนั้นใช้คำสั่ง dataset เพื่อตรวจสอบผลการกำหนดค่าอีกครั้ง จะพบว่าตอนนี้ค่า Channel, Ext PAN ID, Mesh Local Prefix, Network Key, Network Name, PAN ID ของฝั่ง ESP32-C6 ตรงกับ ESP32-H2 แล้ว
ใช้คำสั่ง dataset commit active เพื่อให้การตั้งค่ามีผล (ทั้ง 2 ฝั่ง)
# ทำให้การตั้งค่ามีผล
dataset commit active
ที่ฝั่ง ESP32-H2 ใช้คำสั่งเปิดการใช้งานส่วน Radio และ Thread ดังนี้
# เปิดการใช้งานส่วน Radio
ifconfig up
# เปิดใช้งาน Thread
thread start
จากนั้นใช้คำสั่ง state เพื่อตรวจสอบสถานะปัจจุบันของอุปกรณ์ (พิมพ์หลาย ๆ ครั้ง) โดยหากขึ้น leader แสดงว่า ESP32-H2 พร้อมให้ ESP32-C6 (อุปกรณ์ปลายทาง-End Device) เข้ามาเชื่อมต่อแล้ว
ใช้คำสั่งแบบเดียวกับฝั่ง ESP32-C6 แต่ครั้งนี้จะขึ้นเป็น child หมายถึงไปเชื่อมต่อกับ ESP32-H2 สำเร็จแล้ว
หมายเหตุ. กรณีขึ้นเป็น leader แสดงว่า ESP32-C6 เชื่อมต่อเครือข่ายไม่สำเร็จ อาจเกิดจากการตั้งค่าบางส่วนไม่ตรงกัน ให้ใช้คำสั่ง thread stop ตามด้วย ifconfig down แล้วตามด้วย dataset แล้วตรวจสอบให้แน่ใจว่าการกำหนดค่าต่าง ๆ ตรงกับของ ESP32-H2
ใช้คำสั่ง ipaddr เพื่อตรวจสอบหมายเลข IP ของทั้ง 2 อุปกรณ์
# ตรวจสอบหมายเลข IP ของอุปกรณ์
ipaddr
ที่ฝั่ง ESP32-C6 (Child) จะพบว่ามี IP ขึ้นมา 3 ตัว ส่วนฝั่ง ESP32-H2 (Leader) จะพบว่ามีขึ้นมา 4 ตัว
ทดสอบใช้คำสั่ง ping ไปยังอุปกรณ์ฝั่งตรงข้าม โดยใช้ IP Mesh-Local EID (ML-EID) ในการ Ping
# ทดสอบ ping ไปที่อุปกรณ์ฝั่งตรงข้าม
ping <Mesh-Local EID (ML-EID)>
จะพบว่าสามารถ Ping หากันได้แล้ว
ทดสอบสื่อสารกันผ่านโปรโตคอล UDP โดยให้ฝั่ง ESP32-H2 เปิดพอร์ต 20617 รอรับข้อมูลไว้
// เปิดใช้โปรโตคอล UDP
udp open
// เปิดพอร์ต 20617 รอรับข้อมูลเข้ามา
udp bind :: 20617
ที่ฝั่ง ESP32-C6 ทดสอบส่งข้อความ "ESP" เข้าไป โดยใช้คำสั่ง
// เปิดใช้โปรโตคอล UDP
udp open
// ส่งข้อความ "ESP" ไปที่ ESP32-H2 udp send <Mesh-Local EID ของ ESP32-H2> 20617 ESP
ผลที่ได้ จะเห็นว่าฝั่ง ESP32-H2 สามารถรับข้อมูลเข้ามาได้แล้ว
Arduino Core for ESP32 ได้จัดเตรียมคำสั่ง OThreadCLI.println() ไว้สำหรับการส่งคำสั่งเข้าไปใน CLI ให้แล้ว แทนที่จะพิมพ์คำสั่งทีละบรรทัดลงไปใน Terminal ก็ใช้โค้ดโปรแกรมในการพิมพ์แทน
การรับข้อความจาก Terminal ใช้คำสั่ง OThreadCLI.readStringUntil('\n') ได้ค่ากลับมาเป็นข้อความ 1 บรรทัด สามารถใช้เงื่อนไข if เพื่อเปรียบเทียบสตริง หรือดึงข้อมูลในข้อความออกมาใช้งานได้เลย
ผู้เขียนได้ทำโค้ดโปรแกรมตัวอย่างไว้ให้แล้ว ดังนี้
จากคำสั่งที่ได้เรียนรู้ไปในหัวข้อที่ 5) ลองเล่น Thread ผ่าน CLI ร่วมกับการใช้คำสั่ง OThreadCLI.println() ได้โค้ดโปรแกรมฝั่งรับข้อมูลจาก UDP ดังนี้
อัพโหลดโปรแกรมลงบอร์ด จากนั้นเปิด Serial Monitor ขึ้นมา ปรับ 115200 baud แล้วกดปุ่ม Reset บนบอร์ด 1 ครั้ง จะพบข้อความ Done จากการส่งคำสั่งต่าง ๆ เข้า CLI ขึ้นมารัว ๆ
พิมพ์คำสั่ง state ลงช่อง Message แล้วกดปุ่ม Enter บนคีย์บอร์ด สถานะของอุปกรณ์จะขึ้นมา
รอจนกว่าจะขึ้น leader แล้วจึงอัปโหลดโปรแกรม / เปิดบอร์ด ESP32-C6 ขึ้นมา
จากคำสั่งที่ได้เรียนรู้ไปในหัวข้อที่ 5) ลองเล่น Thread ผ่าน CLI ร่วมกับการใช้คำสั่ง OThreadCLI.println() ได้โค้ดโปรแกรมฝั่งส่งข้อมูลเข้า UDP ดังนี้
อัปโหลดโปรแกรมลงบอร์ด จากนั้นเปิด Serial Monitor ขึ้นมา ปรับ 115200 baud แล้วกดปุ่ม Reset บนบอร์ด 1 ครั้ง จะพบข้อความ Done จากการส่งคำสั่งต่าง ๆ เข้า CLI ขึ้นมารัว ๆ
หลังเชื่อมต่อกับเครือข่ายได้ จะมีข้อความ Done แสดงต่อเนื่อง จากการใช้คำสั่ง udp send ส่วนฝั่ง ESP32-H2 แสดงข้อความแจ้งได้รับข้อมูลเข้ามาผ่าน UDP
แก้ไขโค้ดโปรแกรมฝั่ง ESP32-H2 ดังนี้
แก้ไขโค้ดโปรแกรมฝั่ง ESP32-C6 ดังนี้
ทดสอบกดปุ่ม BOOT บน ESP32-C6 จะพบว่าหลอดแอลอีดีบนบอร์ด ESP32-H2 จะติด-ดับตามการกดปุ่มแล้ว
การใช้งาน Thread บน ESP32 ตอนนี้ยังไม่มีเอกสารทางการออกมามากนัก ข้อมูลที่ผู้เขียนนำเสนอในบทความนี้เกิดจากการศึกษาโค้ดตัวอย่าง และสรุปข้อมูลมา ดังนั้นอาจมีข้อมูลบางส่วนไม่ถูกต้อง หรือไม่ครบถ้วน แนะนำให้ศึกษาข้อมูลเพิ่มเติมจากลิ้งด้านล่างนี้