Robotic Temi For Disabled person

Robotic Temi For Disabled person

สมาชิกผู้จัดทำ

  1. พีรดนย์ เรืองแก้ว
  2. พงษ์พัฒน์ วงศ์กำแหงหาญ
  3. ธัชพงศ์ สังข์ถาวร

ที่มาและปัญหา

ในปัจจุบัน เทคโนโลยีหุ่นยนต์ยังคงเป็นเรื่องใหม่สำหรับบุคคลทั่วไป หลายคนยังไม่คุ้นเคยกับการใช้หุ่นยนต์เพื่อส่งเสริมอาชีพหรือการดำเนินกิจกรรมในชีวิตประจำวัน เช่น การขายของหรือการประชาสัมพันธ์ ขณะที่เทคโนโลยีหุ่นยนต์มีศักยภาพในการช่วยลดข้อจำกัดด้านแรงงานและเพิ่มความสามารถในการทำงาน โดยเฉพาะอย่างยิ่งสำหรับผู้ที่มีความบกพร่องทางร่างกาย

สำหรับผู้พิการ การดำเนินกิจกรรมทางธุรกิจหรือสังคมมักมีอุปสรรคที่เกี่ยวข้องกับการเคลื่อนไหว เช่น การออกไปพบลูกค้าหรือการเข้าถึงพื้นที่ การขายสินค้าและการประชาสัมพันธ์จึงเป็นเรื่องยากลำบาก นอกจากนี้ การต้องพึ่งพาผู้อื่นยังทำให้พวกเขาสูญเสียโอกาสทางธุรกิจและการสร้างรายได้ ด้วยเทคโนโลยีหุ่นยนต์ควบคุมระยะไกล เช่น หุ่นยนต์ Temi ที่สามารถควบคุมผ่านเว็บไซต์ในเครือข่ายเดียวกัน (LAN) จะช่วยลดอุปสรรคเหล่านี้ ทำให้ผู้พิการสามารถดำเนินกิจกรรมต่าง ๆ ได้อย่างสะดวก

ระบบหุ่นยนต์ Temi นี้จะทำให้ผู้พิการสามารถขายสินค้าโดยที่ลูกค้าสามารถซื้อได้โดยตรงจากหุ่นยนต์ หรือประชาสัมพันธ์ข้อมูลผ่านหน้าจอของ Temi ได้อย่างมีประสิทธิภาพ นอกจากนี้ ยังช่วยให้บุคคลทั่วไปได้เรียนรู้และคุ้นเคยกับการใช้งานหุ่นยนต์ในการทำงานและส่งเสริมอาชีพ เป็นการนำเทคโนโลยีมาใช้เพื่อเพิ่มความสามารถในการดำเนินธุรกิจและกิจกรรมทางสังคม ทั้งนี้ ระบบนี้ยังมีศักยภาพในการช่วยให้ผู้พิการทำงานได้อย่างอิสระและเป็นแรงบันดาลใจในการนำเทคโนโลยีมาใช้เพื่อส่งเสริมอาชีพในอนาคต

วัตถุประสงค์

1.เพื่อช่วยเหลือคนพิการให้สามารถขายของ,ประชาสัมพันธ์ได้ด้วยหุ่นยนต์ที่ควบคุมจากระยะไกล

2.เพื่อศึกษาการทำงานเบื้องต้นของการควบคุมหุ่นยนต์ temi ผ่าน website

3.เพื่อทำ project วิชา programming

ขอบเขตการศึกษา

1.การควบคุมหุ่นยนต์ใน LAN เดียวกัน ไม่ต้อง forward port

2.การควบคุมโดยใช้ website

3.kotlin,js,node js,api

แผนการดำเนินการ


ภาพรวมของระบบ (system overview)

โครงการนี้มีเป้าหมายในการพัฒนาระบบหุ่นยนต์โดยใช้ Temi ซึ่งออกแบบมาเฉพาะสำหรับผู้พิการ โดยมีคุณสมบัติหลักดังนี้:

  • การควบคุมระยะไกล: ผู้พิการสามารถควบคุมหุ่นยนต์ Temi จากระยะไกลผ่านเว็บแอปพลิเคชันได้อย่างสะดวก
  • การสนับสนุนกิจกรรม: ในขณะที่ผู้พิการควบคุมหุ่นยนต์ Temi พวกเขาสามารถทำกิจกรรมต่าง ๆ เช่น การขายสินค้า หรือการประชาสัมพันธ์ (PR) โดยไม่จำเป็นต้องลุกจากที่นั่ง ซึ่งเหมาะสำหรับผู้พิการที่ใช้รถเข็น
    • ผู้พิการสามารถควบคุมหุ่นยนต์เพื่อขายสินค้าหรือทำการประชาสัมพันธ์ผ่านเว็บแอปได้ ในขณะที่ Temi ทำหน้าที่ตอบสนองคำสั่งต่าง ๆ
    • ผู้พิการสามารถควบคุมหุ่นยนต์และสื่อสารแบบสองทาง รวมถึงการมอนิเตอร์ผ่าน Temi และการส่งข้อมูลหรือข้อความประชาสัมพันธ์จากเว็บแอปไปยัง Temi ได้อย่างมีประสิทธิภาพ

จากภาพ System Overview ที่แสดงในรูปแบบล่าสุด สามารถแบ่งการทำงานออกเป็น 3 ส่วนหลัก คือ Front-endBack-end, และ Temi Android App โดยการทำงานแต่ละส่วนมีความเชื่อมโยงกันเพื่อให้ผู้พิการสามารถควบคุมหุ่นยนต์ Temi และทำกิจกรรมต่าง ๆ ได้อย่างสะดวก:

1. Front-end (Web Application)

  • Manage Data: ผู้พิการสามารถจัดการข้อมูล เช่น อัปเดตสินค้าหรือข้อมูลที่จำเป็น ผ่านเว็บแอป โดยคำสั่งการจัดการข้อมูลจะถูกส่งไปยัง Back-end เพื่อทำการประมวลผล
  • Control: ฟีเจอร์นี้ทำให้ผู้พิการสามารถควบคุมการเคลื่อนที่ของหุ่นยนต์ Temi ได้โดยตรงจากเว็บแอป
  • Video: ส่งข้อมูลวิดีโอแบบทางเดียว (One-Way Video) จากหุ่นยนต์ Temi ไปยังผู้พิการหรือผู้ดูแล
  • Voice: รองรับการสื่อสารแบบสองทางผ่านเสียงระหว่างผู้พิการและหุ่นยนต์หรือบุคคลอื่น โดยใช้การสื่อสารผ่านระบบ Zoom เพื่อให้ผู้พิการสามารถติดต่อสื่อสารได้อย่างมีประสิทธิภาพ

2. Back-end (API และ Database)

  • PostgreSQL Database: ทำหน้าที่เก็บข้อมูลทั้งหมด เช่น ข้อมูลสินค้าหรือข้อมูลที่ประชาสัมพันธ์
  • REST API: ใช้สำหรับเชื่อมต่อข้อมูลระหว่าง Front-end และ Back-end โดยส่งคำสั่งและรับข้อมูลเพื่อตอบสนองคำสั่งจากผู้พิการ
  • MQTT Protocol: รองรับการส่งคำสั่งควบคุมจาก Front-end จาก REST API ผ่านการเชื่อมต่อ MQTT Protocol ไปยัง Temi
  • WebSocket: รับข้อมูลวิดีโอแบบทางเดียว (One-Way Video) จากหุ่นยนต์ โดยเชื่อมต่อกับ Raspberry Pi และ กล้อง webcam สำหรับมอนิเตอร์หุ่นยนต์จากระยะไกล

3. Temi Android App

  • UI: แอปพลิเคชันบน Android ที่ติดตั้งบนหุ่นยนต์ Temi ทำหน้าที่แสดงผลและโต้ตอบกับผู้ใช้ โดยผู้พิการสามารถสั่งงานหรือทำกิจกรรมต่าง ๆ ผ่านการควบคุมนี้
  • Temi Controller: ควบคุมการเคลื่อนที่และการทำงานของหุ่นยนต์ Temi โดยเชื่อมต่อกับ Raspberry Pi และ Webcam ที่ติดตั้งบนหุ่นยนต์เพื่อการมอนิเตอร์และรับ-ส่งข้อมูล

4. เครื่องมือเพิ่มเติม (Additional Tools)

  • Zoom: ถูกใช้ในการสื่อสารแบบสองทางผ่านวิดีโอและเสียง เพื่อให้ผู้พิการสามารถติดต่อสื่อสารกับลูกค้าหรือบุคคลอื่นผ่านหุ่นยนต์ Temi

ในภาพรวม ระบบนี้ออกแบบมาเพื่อให้ผู้พิการสามารถควบคุมหุ่นยนต์ Temi ได้จากระยะไกลผ่านเว็บแอป พร้อมทั้งสนับสนุนการทำกิจกรรมต่าง ๆ เช่น การขายสินค้า การประชาสัมพันธ์ และการสื่อสารแบบสองทางอย่างมีประสิทธิภาพ


ภาพรวมของระบบ (System Scenario)

User System Scenario

จาก User System Scenario ที่แสดงในภาพ มีการอธิบายการทำงานของระบบจากมุมมองของผู้พิการที่ใช้งานหุ่นยนต์ Temi โดยในภาพจะแสดงถึงการเชื่อมต่อและการโต้ตอบระหว่างผู้ใช้งาน (Disabled Person) กับหุ่นยนต์ Temi ผ่านระบบต่าง ๆ

รายละเอียดของระบบตามที่แสดง:

1. Voice Communication (การสื่อสารด้วยเสียง)

  • ผู้พิการสามารถพูดและรับฟังการตอบกลับได้จากระบบการสื่อสารด้วยเสียง ซึ่งเชื่อมต่อกับฟีเจอร์ Zoom เพื่อรองรับการสื่อสารแบบสองทาง (Two-way communication)

2. Web-application (แอปพลิเคชันบนเว็บ)

  • Video: ระบบวิดีโอที่เชื่อมต่อกับกล้องของหุ่นยนต์ Temi เพื่อให้ผู้พิการสามารถรับข้อมูลแบบเรียลไทม์ได้
  • Manage Data: ผู้พิการสามารถจัดการข้อมูลต่าง ๆ เช่น อัปเดตข้อมูลเพื่อแสดงบนหน้าจอของหุ่นยนต์ Temi
  • Control: ฟีเจอร์ควบคุมการเคลื่อนไหวของหุ่นยนต์ Temi ผู้ใช้งานสามารถสั่งการให้หุ่นยนต์เคลื่อนที่และได้รับการตอบกลับในทันทีผ่านแอปบนเว็บ

3. Temi Robot

  • Webcam: หุ่นยนต์ Temi มีการติดตั้งกล้องเพื่อส่งภาพให้กับผู้พิการที่ใช้ระบบผ่านการมอนิเตอร์จากระยะไกล
  • Control Robot Movement: หุ่นยนต์ Temi จะตอบสนองต่อคำสั่งที่ได้รับจาก Web-app ในการเคลื่อนที่และแสดงข้อมูล
  • การแสดงข้อมูล (Show Information): ข้อมูลที่ถูกจัดการในระบบจะถูกแสดงบนหน้าจอของหุ่นยนต์ตามคำสั่งที่ได้รับ

Customer System Scenario

จาก Customer System Scenario ที่แสดงในภาพ เราสามารถเห็นการทำงานที่เชื่อมโยงระหว่าง ลูกค้า กับ หุ่นยนต์ Temiโดยมีการโต้ตอบผ่านฟีเจอร์หลักหลายอย่าง เพื่อสร้างประสบการณ์ที่ดีให้กับลูกค้า และในขณะเดียวกันก็แสดงข้อมูลให้ลูกค้าได้รับรู้

รายละเอียดของระบบตามที่แสดง:

1. Robot Side (ฝั่งหุ่นยนต์)

  • Voice Speaker (การสื่อสารด้วยเสียง): หุ่นยนต์ Temi สามารถใช้เสียงเพื่อให้ข้อมูลกับลูกค้าได้ เช่น การตอบคำถามหรือการให้ข้อมูลเกี่ยวกับสินค้า โดยใช้ลำโพงเพื่อสื่อสารข้อมูลในรูปแบบเสียง
  • Movement (การเคลื่อนไหว): หุ่นยนต์สามารถเคลื่อนที่ตามคำสั่ง เช่น การเลื่อนเข้าไปใกล้ลูกค้าหรือหมุนเพื่อแสดงข้อมูลเพิ่มเติม
  • Temi Display: หน้าจอแสดงผลของหุ่นยนต์ Temi ถูกใช้เพื่อแสดงข้อมูลต่าง ๆ ให้ลูกค้าเห็น เช่น ใบหน้าหุ่นยนต์ที่ออกแบบให้ดูน่ารัก (Cute Robot Face) เพื่อสร้างความประทับใจแรกแก่ลูกค้า
    • Show Information: หุ่นยนต์จะแสดงข้อมูลที่สำคัญ เช่น ข้อมูลสินค้า, ข้อมูลประชาสัมพันธ์ หรือข้อความอื่น ๆ ที่เกี่ยวข้อง เพื่อให้ลูกค้าได้รับรู้ผ่านหน้าจอ

2. Customer Side (ฝั่งลูกค้า)

  • Get Information (การรับข้อมูล): ลูกค้าสามารถรับข้อมูลจากหุ่นยนต์ Temi ได้ทั้งในรูปแบบเสียงและข้อความที่แสดงบนหน้าจอ
    • Get Voice Information: ลูกค้าจะได้รับข้อมูลในรูปแบบเสียงผ่านลำโพงของหุ่นยนต์
    • Get Text Information: ข้อมูลที่เป็นข้อความจะแสดงผ่านหน้าจอ Temi
  • First Impression (ความประทับใจแรก): ลูกค้าจะสร้างความประทับใจแรกต่อหุ่นยนต์ผ่านลักษณะท่าทาง การเคลื่อนไหว และข้อมูลที่หุ่นยนต์นำเสนอ
  • Interactive with Robot (การโต้ตอบกับหุ่นยนต์): ลูกค้าสามารถโต้ตอบกับหุ่นยนต์ได้ผ่านหน้าจอสัมผัส (Touch Interaction) เช่น การแตะเพื่อเลือกรายการหรือขอข้อมูลเพิ่มเติม
  • UI (ส่วนติดต่อผู้ใช้): การโต้ตอบระหว่างลูกค้ากับหุ่นยนต์ Temi ผ่าน UI บนหน้าจอ โดยการแสดงข้อมูลและฟีเจอร์ต่าง ๆ ที่ทำให้ลูกค้าเข้าใจและใช้ประโยชน์จากหุ่นยนต์ได้อย่างง่ายดาย

User Flow ของการใช้งาน Temi UI: ประชาสัมพันธ์และขายสินค้า

User Flow ของการใช้งาน Temi UI ซึ่งถูกออกแบบมาเพื่อให้หุ่นยนต์ Temi ทำหน้าที่ในสองกรณีหลัก ๆ คือ การประชาสัมพันธ์ และ การขายสินค้า โดยทั้งสองกรณีนี้มีการโต้ตอบผ่านหน้าจอ UI ของ Temi ที่ง่ายต่อการใช้งานสำหรับลูกค้า

1. การโต้ตอบระหว่างลูกค้าและ Temi ผ่าน UI

การใช้งานเริ่มต้นเมื่อผู้ใช้งาน (ลูกค้า) โต้ตอบกับหุ่นยนต์ Temi ผ่านหน้าจอของหุ่นยนต์ ซึ่งจะมีอินเทอร์เฟซ (UI) แสดงขึ้นมาเพื่อให้ลูกค้าเลือกการทำงาน ไม่ว่าจะเป็นการประชาสัมพันธ์หรือการขายสินค้า โดยเมื่อมีการเลือกคำสั่ง Temi จะทำการแสดงข้อมูลที่ตรงกับกรณีที่ลูกค้าเลือก

2. การทำงานของ Temi ในแต่ละกรณี (Case)

2.1 Case การประชาสัมพันธ์

ในกรณีที่ลูกค้าเลือก การประชาสัมพันธ์ หุ่นยนต์ Temi จะทำการแสดงข้อมูลต่าง ๆ เช่น โปสเตอร์งานหรือกิจกรรมที่กำลังจะเกิดขึ้น โดยลูกค้าสามารถ:

  • ดูรายละเอียดเพิ่มเติม: เมื่อลูกค้าสนใจในกิจกรรมที่แสดงอยู่ ลูกค้าสามารถกดเพื่อดูรายละเอียดเพิ่มเติมเกี่ยวกับกิจกรรมได้ เช่น วันเวลา สถานที่จัดงาน และข้อมูลที่เป็นประโยชน์อื่น ๆ
  • ปิดหน้าจอการแสดงผล: หากลูกค้าต้องการปิดหน้าจอ สามารถกดปุ่ม “Close” เพื่อกลับไปยังหน้าหลัก

2.2 Case การขายสินค้า

ในกรณีที่ลูกค้าต้องการ ซื้อสินค้า หุ่นยนต์ Temi จะทำหน้าที่เป็นตัวกลางในการแสดงรายการสินค้า โดยแต่ละสินค้าจะแสดงรูปภาพ ราคา และคำอธิบายสินค้า โดยลูกค้าสามารถ:

  • ดูรายละเอียดสินค้า: เมื่อสนใจสินค้าชิ้นใด ลูกค้าสามารถกดเพื่อดูรายละเอียดเพิ่มเติม เช่น ข้อมูลสินค้า รูปภาพเพิ่มเติม และราคาของสินค้า
  • สั่งซื้อสินค้า: เมื่อสินค้าถูกเลือก ลูกค้าสามารถสั่งซื้อโดยการสแกน QR Code ที่ปรากฏบนหน้าจอเพื่อชำระเงินได้ทันที
  • ปิดหน้าจอการแสดงผล: เมื่อการทำรายการเสร็จสิ้น ลูกค้าสามารถกด “Close” เพื่อกลับไปยังหน้าแรก

3. การทำงานของหุ่นยนต์ Temi

หลังจากที่ลูกค้าได้โต้ตอบกับหน้าจอของ Temi ไม่ว่าจะเป็นการเลือกดูข้อมูลประชาสัมพันธ์หรือซื้อสินค้า Temi จะทำการ ยกหัว (Head-up) เพื่อแสดงข้อมูลที่เลือก โดยจะทำการปรับมุมของหน้าจอให้ตรงกับระดับสายตาของลูกค้า ทำให้การโต้ตอบมีความเป็นธรรมชาติและสะดวกสบาย


การนำเสนอผลการออกแบบ (Design)

การออกแบบชั้นวางตระกร้าเอาไว้ขายสินค้า

Dimention

ตัวอย่างการพิม 3d printer

Temi Robot Demention

Camera Dimention

– Height x Width x Depth: 29 mm x 94 mm x 24 mm.
– Cable Length: 1.5 m.
– Height x Width x Depth: 43.3 mm x 94 mm x 71 mm

Raspberry pi 4 Dimention


การออกแบบทางโปรแกรม (Design Programing System)

การออกแบบทางโปรแกรมจะแบ่งเป็น 3 ส่วนหลักๆได้แก่ Front-end, Back-end และ Temi Andriod App:

Front-end:

ในส่วนของ Front-end จะเป็นการออกแบบ web-app ให้สามารถรองรับการ ควมคุมหุ่นยนต์ผ่าน keyboard กาาร monitor และการจัดการข้อมูลโดยจะขอแบ่งหน้า web-app ออกเป็นสองหน้า หน้าแรกได้แก้ การควมคุมผ่าน keyboard และ การmonitorหุ่นยนต์ ผ่านหน้าแรก:

1. หน้าแรก: การควมคุมและการมอนิเตอร์

การควมคุมผ่าน keyboard สำหรับการควมคุม movement ของ Robot

โค้ดส่วนนี้ทำงานได้ดี โดยใช้คีย์บอร์ดในการควบคุมการเคลื่อนที่ของหุ่นยนต์ (เดินหน้า, ถอยหลัง, เลี้ยวซ้าย, เลี้ยวขวา) และสามารถส่งคำสั่งไปยัง API ที่เชื่อมต่อกับหุ่นยนต์ Temi ได้อย่างถูกต้อง โดยการฟังเหตุการณ์จากปุ่มคีย์บอร์ด (keydown) ผ่าน React Hooks เช่น useState และ useEffect

const Controller = () => {
  const [controlEnabled, setControlEnabled] = useState(false); // สถานะการควบคุมหุ่นยนต์

  const handleKeyDown = (event) => {
    if (!controlEnabled) return; // ตรวจสอบว่าเปิดการควบคุมอยู่หรือไม่

    let newCommand;

    // ตรวจสอบคีย์ที่กดและกำหนดคำสั่ง
    switch (event.key.toLowerCase()) {
      case 'w':
        newCommand = 'MOVE_FORWARD'; // คำสั่งเดินหน้า
        break;
      case 's':
        newCommand = 'MOVE_BACKWARD'; // คำสั่งถอยหลัง
        break;
      case 'a':
        newCommand = 'MOVE_LEFT'; // คำสั่งเลี้ยวซ้าย
        break;
      case 'd':
        newCommand = 'MOVE_RIGHT'; // คำสั่งเลี้ยวขวา
        break;
      case 'x':
      case 'z':
      case 'c':
        newCommand = event.key.toUpperCase(); // คำสั่งอื่น ๆ
        break;
      default:
        return; // ถ้าไม่ได้กดคีย์ที่เกี่ยวข้องกับคำสั่ง ให้ข้ามไป
    }

    // ส่งคำสั่งไปยัง API ที่เชื่อมต่อกับหุ่นยนต์
    axios.post(`${API}/send-command`, { command: newCommand })
      .then(response => console.log('Command sent:', response.data))
      .catch(error => console.error('Error sending command:', error)); // จัดการข้อผิดพลาดถ้ามี
  };

  // เพิ่ม event listener สำหรับการกดปุ่ม
  useEffect(() => {
    window.addEventListener('keydown', handleKeyDown);

    // ลบ event listener เมื่อ component ถูกทำลาย
    return () => {
      window.removeEventListener('keydown', handleKeyDown);
    };
  }, [controlEnabled]);

  // ฟังก์ชันสำหรับเปิดหรือปิดการควบคุมหุ่นยนต์
  const toggleControl = () => {
    setControlEnabled(!controlEnabled);
  };

  return (
    <div>
      <button onClick={toggleControl}>
        {controlEnabled ? 'Disable Control' : 'Enable Control'}
      </button>
    </div>
  );
};

การควมคุมผ่าน button สำหรับการควมคุม movement head ของ Robot

โค้ดส่วนนี้ทำงานได้ดี โดยใช้ปุ่มบนหน้าเว็ยในการควบคุมการเคลื่อนไหวหัสของหุ่นยนต์ (เงยขึ้น,เงยลง) และสามารถส่งคำสั่งไปยัง API ที่เชื่อมต่อกับหุ่นยนต์ Temi ได้อย่างถูกต้อง

  const handelButtonHeadRobotUp = () => {
    axios.post(`${API}/send-command-head`, { command: 'HEAD_UP' })
      .then(response => console.log('Command sent:', response.data))
      .catch(error => console.error('Error sending command:', error));
  }

  const handelButtonHeadRobotDown = () => {
    axios.post(`${API}/send-command-head`, { command: 'HEAD_DOWN' })
      .then(response => console.log('Command sent:', response.data))
      .catch(error => console.error('Error sending command:', error));
  }

การ monitor หุ่นยนต์:

ในส่วนของการ monitor หุ่นยนต์ เราจะใช้การเชื่อมต่อผ่าน WebSocket เพื่อรับข้อมูลวิดีโอแบบเรียลไทม์จากหุ่นยนต์ Temiโดยข้อมูลวิดีโอที่ได้รับจาก WebSocket จะเป็นภาพในรูปแบบ Base64-encoded JPEG และจะถูกแสดงผลบนหน้าเว็บผ่าน canvas ใน React component ที่ชื่อว่า Video ดังที่แสดงในโค้ด

การ monitor หุ่นยนต์ Temi ผ่านเว็บแอปใช้การเชื่อมต่อ WebSocket เพื่อรับข้อมูลวิดีโอแบบเรียลไทม์ในรูปแบบของ Base64-encoded JPEG จากหุ่นยนต์ และแสดงผลผ่าน canvas บนหน้าเว็บ มีการทำงานหลักดังนี้:

  1. จัดการสถานะการเชื่อมต่อ: แจ้งเตือนผู้ใช้เมื่อมีการเชื่อมต่อหรือถูกตัดการเชื่อมต่อ
  2. เชื่อมต่อ WebSocket: เปิดการเชื่อมต่อ WebSocket เพื่อรับข้อมูลวิดีโอ
  3. แสดงผลวิดีโอ: เมื่อได้รับข้อมูลวิดีโอ (Base64-encoded JPEG) จะแสดงผลใน canvas บนหน้าเว็บ
  4. การอัปเดตวิดีโอเรียลไทม์: ทุกครั้งที่มีข้อมูลใหม่ วิดีโอจะถูกอัปเดตให้ผู้ใช้สามารถ monitor หุ่นยนต์ได้แบบเรียลไทม์
import React, { useEffect, useRef, useState } from 'react';

const Video = () => {
  const websocketUrl = "ws://10.250.70.220:8765"; // URL สำหรับ WebSocket
  const canvasRef = useRef(null); // อ้างอิงไปยัง element canvas เพื่ออัปเดตภาพ

  useEffect(() => {
    const ws = new WebSocket(websocketUrl); // สร้างการเชื่อมต่อ WebSocket

    ws.onmessage = (event) => {
      const imgSrc = `data:image/jpeg;base64,${event.data}`;
      const img = new Image();
      img.src = imgSrc;

      img.onload = () => {
        const canvas = canvasRef.current;
        const context = canvas.getContext('2d');
        context.drawImage(img, 0, 0, canvas.width, canvas.height); // วาดภาพใหม่บน canvas
      };
    };

    return () => {
      ws.close(); // ปิดการเชื่อมต่อเมื่อ component ถูก unmount
    };
  }, [websocketUrl]);

  return (
    <canvas ref={canvasRef} width="640" height="480" />
  );
};

export default Video;

โค้ดนี้แสดงถึงการเชื่อมต่อ WebSocket และการรับข้อมูลวิดีโอแบบเรียลไทม์เพื่อนำมาวาดบน canvas ซึ่งช่วยให้ผู้ใช้สามารถ monitor หุ่นยนต์ได้อย่างมีประสิทธิภาพ

หลังจากทดสอบการส่งและรับข้อมูลของสอง compoment จากนั้นนำมารวมกันเพื่อใช้แสดงผลในหน้าแรกดังภาพข้างล่าง:


2. หน้าสอง: การจัดการข้อมูล (Data Management)

ในหน้าการจัดการข้อมูล (Data Management) ของเว็บแอปนี้ ผู้ใช้งานสามารถจัดการข้อมูลเพื่อแสดงผลบนหุ่นยนต์ Temiโดยผ่านฟีเจอร์ต่างๆ เช่นการเพิ่ม ลบ หรือแก้ไขข้อมูลสินค้า พร้อมทั้งจัดการภาพสินค้า และส่งคำสั่งอัปเดตไปยังหุ่นยนต์ Temi ทันทีหลังจากที่มีการเปลี่ยนแปลงข้อมูลสินค้า โดยการเชื่อมต่อผ่าน API สามารถอธิบายหลักการทำงานได้ดังนี้:

  • การดึงข้อมูล: เมื่อเปิดหน้าจอ ฟังก์ชันจะดึงข้อมูลสินค้าจากฐานข้อมูลและแสดงในตารางข้อมูลเพื่อให้ผู้ใช้สามารถจัดการข้อมูลได้
  • การจัดการข้อมูล: ผู้ใช้สามารถทำการ เพิ่ม (Add), แก้ไข (Edit), และ ลบ (Delete) ข้อมูลสินค้าโดยใช้ฟังก์ชันที่กำหนด
  • การพรีวิวภาพ: ผู้ใช้สามารถพรีวิวภาพสินค้าหรือ QR Code ที่เชื่อมต่อกับข้อมูลสินค้าผ่านปุ่มที่เกี่ยวข้อง
  • การอัปเดตคำสั่ง: เมื่อมีการเปลี่ยนแปลงข้อมูล คำสั่งจะถูกส่งไปยังหุ่นยนต์ Temi ผ่าน API เพื่ออัปเดตข้อมูลบนหุ่นยนต์ในทันที
const DataManagement = () => {
  const dispatch = useDispatch();
  const data = useSelector(selectData); // ดึงข้อมูลจาก Redux
  const [localData, setLocalData] = useState([]); 
  const [selectedRows, setSelectedRows] = useState([]);

  // ดึงข้อมูลสินค้าจาก API เมื่อเปิดหน้าจอ
  useEffect(() => {
    dispatch(fetchDataStock());
  }, [dispatch]);

  // อัปเดตข้อมูลใน state
  useEffect(() => {
    setLocalData(data);
  }, [data]);

  // ฟังก์ชันสำหรับลบข้อมูล
  const handleDelete = () => {
    const updatedData = localData.filter(product => !selectedRows.includes(product.id));
    setLocalData(updatedData);
    selectedRows.forEach(rowId => dispatch(deleteProduct(rowId)));

    // ส่งคำสั่งไปยัง API เพื่ออัปเดตข้อมูลในหุ่นยนต์
    axios.post(`${API}/update-store`, { command: 'UPDATE' })
      .then(response => console.log('Command sent:', response.data))
      .catch(error => console.error('Error sending command:', error));
  };

  // ฟังก์ชันสำหรับเพิ่มข้อมูล
  const handleSaveAdd = (newProduct) => {
    dispatch(addProduct(newProduct)).unwrap().then((addedProduct) => {
      setLocalData([...localData, addedProduct]);
    });

    // ส่งคำสั่งไปยัง API เพื่ออัปเดตข้อมูลในหุ่นยนต์
    axios.post(`${API}/update-store`, { command: 'UPDATE' })
      .then(response => console.log('Command sent:', response.data))
      .catch(error => console.error('Error sending command:', error));
  };

  return (
    <div>
      {/* แสดงข้อมูลสินค้าในตาราง */}
      <DataGrid rows={localData} columns={columns} checkboxSelection onSelectionModelChange={(newSelection) => setSelectedRows(newSelection)} />

      {/* ปุ่มเพิ่ม แก้ไข และลบสินค้า */}
      <button onClick={handleDelete}>Delete</button>
      {/* ปุ่มอื่นๆเช่นเพิ่มและแก้ไขสามารถเพิ่มได้ในภายหลัง */}
    </div>
  );
};

export default DataManagement;

จาก โค้ดสามารถอธิบายการทำงานหลักได้ดังนี้:

  • การดึงข้อมูล: เมื่อหน้าเว็บถูกเปิด ฟังก์ชัน useEffect จะทำงานเพื่อดึงข้อมูลสินค้าจาก API ผ่านการเรียกใช้ fetchDataStock และข้อมูลที่ได้จะถูกจัดเก็บไว้ใน Redux และถูกส่งไปแสดงในตาราง (DataGrid)
  • การลบข้อมูล: เมื่อผู้ใช้เลือกสินค้าที่ต้องการลบและกดปุ่ม Delete ข้อมูลสินค้านั้นจะถูกลบออกจาก Redux และฐานข้อมูล จากนั้นคำสั่งจะถูกส่งไปยัง API เพื่อให้หุ่นยนต์ Temi อัปเดตข้อมูลใหม่
  • การเพิ่มข้อมูล: ผู้ใช้สามารถเพิ่มสินค้าผ่านฟังก์ชัน handleSaveAdd โดยเมื่อมีการเพิ่มสินค้าใหม่ ข้อมูลจะถูกส่งไปยัง Redux และอัปเดตในฐานข้อมูล รวมถึงส่งคำสั่งไปยังหุ่นยนต์เพื่อแสดงผลข้อมูลใหม่

โดยสรุปแล้ว หน้า Data Management ทำหน้าที่จัดการข้อมูลสินค้าเพื่อแสดงผลบนหุ่นยนต์ Temi โดยใช้ Redux ในการจัดการสถานะของข้อมูลและ API สำหรับการเชื่อมต่อกับหุ่นยนต์

หลังจากทดสอบการแสดงผลและการรับส่งข้อมูลแล้วสามารถใช้ได้ต่อจากนั้นนำมาเพื่อใช้แสดงผลในหน้าแรกดังภาพข้างล่าง:

ตัวอย่างเมื่อกดปุ่ม preview image

ตัวอย่างหน้าการเพิ่มข้อมูล:


Back-end:

ในส่วนของ Back-end สำหรับระบบนี้ มีการทำงานหลักอยู่สามส่วนสำคัญ ได้แก่ APIDatabaseMQTT Protocol และ WSS  ซึ่งทำหน้าที่ร่วมกันในการจัดการข้อมูลและการควบคุมหุ่นยนต์ Temi ดังนี้:

1. API:

เป็นช่องทางที่เว็บแอป (Front-end) ใช้เพื่อส่งและรับข้อมูลจากเซิร์ฟเวอร์ ซึ้ง API จะทำหน้าที่รับคำสั่ง เช่น การเพิ่ม ลบ หรือแก้ไขข้อมูลสินค้า และยังเชื่อมต่อกับ Temi เพื่อส่งคำสั่งควบคุม เช่น การอัปเดตข้อมูลที่แสดงผลบนหน้าจอหุ่นยนต์ และเป็นตัวหลางระหว่าง Front-end และ Mqtt protcol เพื่อส่งข้อมูลไป Mqtt

จากภาพ diagram ที่แสดงให้เห็นถึงการทำงานของ API นั้น เราสามารถอธิบายเพิ่มเติมเกี่ยวกับการทำงานของ API ใน Back-end ของระบบนี้ได้ดังนี้:

  • Web Application (Front-end): ผู้ใช้ในส่วนนี้จะส่งคำขอ (Request) ผ่านเว็บแอปไปยัง API เพื่อเรียกใช้ฟังก์ชันต่าง ๆ เช่น การเพิ่ม ลบ หรืออัปเดตข้อมูลสินค้า รวมถึงการส่งคำสั่งควบคุมไปยังหุ่นยนต์ Temi
  • REST API: เป็นตัวกลางที่ทำหน้าที่รับคำขอจาก Front-end แล้วส่งข้อมูลเหล่านั้นไปยังเซิร์ฟเวอร์เพื่อตรวจสอบ ประมวลผล และส่งคำตอบ (Response) กลับไปที่ Front-end โดย REST API ยังทำหน้าที่ส่งคำสั่งไปยัง MQTT Protocol เพื่อควบคุมการทำงานของหุ่นยนต์ Temi ด้วย
  • Server: เป็นส่วนที่ทำงานอยู่ใน Back-end รับคำสั่งจาก API แล้วทำการเชื่อมต่อกับฐานข้อมูล (Database) เพื่อจัดการข้อมูล และในบางกรณีจะทำการส่งข้อมูลหรือคำสั่งไปยัง MQTT Protocol เพื่อควบคุมหุ่นยนต์ Temi

API ที่ใช้ในโครงการนี้เขียนขึ้นโดยใช้ Node.js และ Express ซึ่งเป็นเฟรมเวิร์กยอดนิยมสำหรับการพัฒนา API โดยในตารางด้านล่างนี้จะเป็นรายละเอียดของแต่ละ API ที่ใช้ในโครงการสำหรับการจัดการข้อมูลและการสื่อสารระหว่างระบบซ

MethodEndpointการทำงาน
POST/send-commandส่งคำสั่งควบคุมการเคลื่อนที่ของหุ่นยนต์ไปยังคิว RabbitMQ(robot_control_queue)
POST/send-command-headส่งคำสั่งควบคุมการเคลื่อนที่ของหัวหุ่นยนต์ (เช่น เงยหัว, ก้ม) ผ่านคิว RabbitMQ (robot_control_head_queue)
POST/update-storeส่งคำสั่งอัปเดตข้อมูลร้านค้าไปยังคิว RabbitMQ(store_update_queue) เพื่อแจ้งเตือนว่ามีการอัปเดตข้อมูล
GET/api/productsดึงข้อมูลสินค้าทั้งหมดจากฐานข้อมูล รวมถึงภาพสินค้าและรายละเอียดต่าง ๆ
GET/api/products/dataดึงข้อมูลสินค้าเฉพาะ metadata (เช่น id, ชื่อ, ราคา, รายละเอียด) โดยไม่มีภาพ
GET/api/products/detail/:idดึงข้อมูลรายละเอียด (detail) ของสินค้าตาม ID
GET/api/productImage/:idดึงภาพสินค้าจากฐานข้อมูลตาม ID และส่งกลับในรูปแบบ Base64-encoded PNG
GET/api/qrCodeImage/:idดึงภาพ QR code ของสินค้าจากฐานข้อมูลตาม ID และส่งกลับในรูปแบบ Base64-encoded PNG
POST/api/products/addเพิ่มสินค้าลงในฐานข้อมูล โดยส่งชื่อ ราคา รายละเอียด ภาพสินค้า และ QR code ผ่าน API
PUT/api/products/updateData/:idอัปเดตข้อมูลสินค้าเฉพาะข้อความและตัวเลข (เช่น ชื่อ, ราคา, รายละเอียด) ตาม ID ของสินค้าในฐานข้อมูล
PUT/api/products/updateProductImage/:idอัปเดตเฉพาะภาพสินค้าของสินค้าตาม ID ของสินค้าในฐานข้อมูล
PUT/api/products/updateQrCodeImage/:idอัปเดตเฉพาะภาพ QR code ของสินค้าตาม ID ของสินค้าในฐานข้อมูล
DELETE/api/products/delete/:idลบสินค้าตาม ID จากฐานข้อมูลร้านค้า

คำอธิบาย API แต่ละตัว

  1. POST /send-command:
    • ส่งคำสั่งควบคุมการเคลื่อนที่ของหุ่นยนต์ Temi โดยใช้คิว RabbitMQ (robot_control_queue) คำสั่งจะถูกส่งจาก Front-end ผ่าน API เพื่อควบคุมการทำงานของหุ่นยนต์
  2. POST /send-command-head:
    • ส่งคำสั่งควบคุมการเงยและก้มของหัวหุ่นยนต์ผ่านคิว RabbitMQ (robot_control_head_queue) คำสั่งจะถูกส่งจาก Front-end เพื่อควบคุมหัวของหุ่นยนต์
  3. POST /update-store:
    • ใช้สำหรับส่งคำสั่งอัปเดตข้อมูลร้านค้าไปยังคิว RabbitMQ (store_update_queue) เพื่อแจ้งให้หุ่นยนต์หรือระบบที่เกี่ยวข้องทราบว่ามีการอัปเดตข้อมูลร้านค้า
  4. GET /api/products:
    • ใช้ในการดึงข้อมูลสินค้าทั้งหมดจากฐานข้อมูล PostgreSQL รวมถึงภาพสินค้า รายละเอียด และราคา ข้อมูลนี้จะถูกใช้เพื่อแสดงใน Front-end หรือหุ่นยนต์ Temi
  5. GET /api/products/data:
    • ใช้ในการดึงข้อมูลสินค้าทั้งหมดจากฐานข้อมูล แต่จะดึงเฉพาะข้อมูล metadata (เช่น ชื่อ ราคา รายละเอียด) โดยไม่มีภาพสินค้า
  6. GET /api/products/detail/:id:
    • ใช้ในการดึงข้อมูลรายละเอียดของสินค้าตาม ID ของสินค้า เช่น คำอธิบายหรือรายละเอียดเพิ่มเติมเกี่ยวกับสินค้า
  7. GET /api/productImage/:id:
    • ใช้ในการดึงภาพสินค้าตาม ID โดยภาพสินค้าจะถูกส่งในรูปแบบ Base64-encoded PNG ซึ่งสามารถแสดงผลในหน้าเว็บได้
  8. GET /api/qrCodeImage/:id:
    • ใช้ในการดึงภาพ QR code ของสินค้าตาม ID ภาพจะถูกส่งในรูปแบบ Base64-encoded PNG ซึ่งสามารถแสดงผลบนหน้าเว็บหรือหน้าจอของหุ่นยนต์ Temi ได้
  9. POST /api/products/add:
    • ใช้สำหรับเพิ่มสินค้าใหม่ลงในฐานข้อมูล โดยส่งข้อมูลสินค้าเช่น ชื่อ ราคา รายละเอียด ภาพสินค้า และ QR code ผ่าน API ไปเก็บในฐานข้อมูล
  10. PUT /api/products/updateData/:id:
    • ใช้ในการอัปเดตข้อมูลสินค้าเฉพาะข้อความและตัวเลข เช่น ชื่อสินค้า ราคา และรายละเอียด โดยข้อมูลจะถูกอัปเดตตาม ID ของสินค้า
  11. PUT /api/products/updateProductImage/:id:
    • ใช้ในการอัปเดตภาพสินค้าเฉพาะ โดยส่งภาพใหม่ในรูปแบบ Base64 และอัปเดตในฐานข้อมูลตาม ID ของสินค้า
  12. PUT /api/products/updateQrCodeImage/:id:
    • ใช้ในการอัปเดตภาพ QR code ของสินค้าตาม ID โดยส่งภาพใหม่ในรูปแบบ Base64 และอัปเดตในฐานข้อมูลตาม ID ของสินค้า
  13. DELETE /api/products/delete/:id:
    • ใช้ในการลบสินค้าตาม ID ออกจากฐานข้อมูลร้านค้า ข้อมูลจะถูกลบออกจากฐานข้อมูลทั้งหมด

2. DataBase (PostgresSQL)

ในส่วนของระบบ Back-end ฐานข้อมูลที่ใช้สำหรับเก็บข้อมูลในโครงการนี้คือ PostgreSQL ซึ่งเป็นฐานข้อมูลที่มีประสิทธิภาพสูงและรองรับการจัดการข้อมูลขนาดใหญ่ ฐานข้อมูลนี้จะทำหน้าที่เก็บข้อมูลสำคัญต่าง ๆ ของระบบ เช่น ข้อมูลสินค้า รายละเอียดสินค้า ภาพสินค้า รวมถึง QR code ของสินค้า หรือข้อมูลประชาสัมพันธ์ เพื่อให้ระบบสามารถจัดการและแสดงข้อมูลเหล่านี้ใน Front-end และหุ่นยนต์ Temi ได้อย่างถูกต้องและรวดเร็ว

ในระบบนี้ใช้ ตารางเดียว สำหรับจัดเก็บข้อมูลหลัก โดยข้อมูลจะถูกรวมไว้ในตารางที่ชื่อว่า DataManagement ซึ่งมีโครงสร้างที่จัดเก็บข้อมูลแบบครบถ้วน เช่น ชื่อสินค้า ภาพสินค้า และรายละเอียด ทำให้สามารถดึงข้อมูลออกมาใช้งานได้อย่างสะดวกและมีประสิทธิภาพ

โดย Database ของระบบนี้จะใช้ code SQL ในการสร้าง Table ดังข้างล่าง:

CREATE TABLE DataManagement (
    id SERIAL PRIMARY KEY,          -- รหัสเอกลักษณ์ของข้อมูล
    name VARCHAR(255),              -- ชื่อของข้อมูลหรือสินค้า
    caption TEXT,                   -- คำบรรยายของข้อมูลหรือสินค้า
    main_image BYTEA,               -- ภาพหลัก
    secondary_image BYTEA,          -- ภาพรองหรือภาพเพิ่มเติม
    detail TEXT,                    -- รายละเอียดเพิ่มเติมเกี่ยวกับข้อมูล
);

ในโครงสร้างฐานข้อมูลของระบบนี้ ตาราง DataManagement ถูกสร้างขึ้นเพื่อจัดการและเก็บข้อมูลหลักของสินค้าและข้อมูลประชาสัมพันธ์ โดยข้อมูลเหล่านี้จะถูกแสดงผลทั้งใน Front-end และหุ่นยนต์ Temi ตารางนี้ประกอบไปด้วยฟิลด์ที่สำคัญต่อการเก็บข้อมูล และมีการกำหนดชนิดข้อมูลให้เหมาะสมเพื่อการใช้งานที่มีประสิทธิภาพ:

คำอธิบายแต่ละฟิลด์ใน DataManagement:

  1. id SERIAL PRIMARY KEY:
    • ฟิลด์นี้เป็นคีย์หลัก (Primary Key) ของตาราง ทำหน้าที่ในการระบุตัวตนของข้อมูลแต่ละรายการในตาราง ซึ่งใช้ประเภทข้อมูล SERIAL โดย PostgreSQL จะทำการเพิ่มตัวเลขอัตโนมัติให้กับฟิลด์นี้ทุกครั้งที่มีการเพิ่มข้อมูลใหม่ ทำให้ไอดีไม่ซ้ำกัน
  2. name VARCHAR(255):
    • ฟิลด์นี้ใช้เก็บชื่อของข้อมูลสินค้า ซึ่งเป็นข้อความ (String) โดยกำหนดให้มีความยาวไม่เกิน 255 ตัวอักษร ฟิลด์นี้จะใช้ในการอ้างอิงหรือแสดงชื่อของข้อมูลที่ถูกจัดเก็บ
  3. caption TEXT:
    • ฟิลด์นี้ใช้สำหรับเก็บคำบรรยายหรือคำอธิบายสั้น ๆ ของสินค้า หรือข้อมูลประชาสัมพันธ์ ฟิลด์นี้ใช้ประเภทข้อมูล TEXT ซึ่งสามารถเก็บข้อความได้ในปริมาณมาก
  4. main_image BYTEA:
    • ฟิลด์นี้ทำหน้าที่เก็บรูปภาพหลักของสินค้า โดยใช้ประเภทข้อมูล BYTEA ซึ่งเหมาะสมสำหรับการเก็บข้อมูลไบนารี เช่น รูปภาพ ฟิลด์นี้จะถูกใช้เพื่อนำภาพไปแสดงในหน้าแอปพลิเคชันหรือหน้าจอของหุ่นยนต์ Temi
  5. secondary_image BYTEA:
    • ฟิลด์นี้ทำหน้าที่เก็บรูปภาพรองหรือรูปภาพเพิ่มเติมของสินค้า ซึ่งมีลักษณะการเก็บข้อมูลคล้ายกับ main_imageเพื่อให้ระบบสามารถแสดงภาพเพิ่มเติมได้
  6. detail TEXT:
    • ฟิลด์นี้ใช้สำหรับเก็บข้อมูลรายละเอียดเพิ่มเติมเกี่ยวกับสินค้า เช่น คำอธิบายสินค้า คุณสมบัติ หรือข้อมูลสำคัญอื่น ๆ ฟิลด์นี้ใช้ประเภท TEXT เพื่อรองรับการเก็บข้อมูลที่ยาวมา

การใช้ประโยชน์จากฐานข้อมูลนี้ในระบบ:

ตาราง DataManagement มีความสำคัญต่อการทำงานของระบบในการจัดการและเก็บข้อมูลสินค้า ข้อมูลประชาสัมพันธ์ หรือรายละเอียดที่ต้องแสดงในหน้าจอของ Temi ข้อมูลทั้งหมดที่เก็บในตารางนี้สามารถถูกดึงมาใช้แสดงผลในหน้าเว็บหรือในแอปพลิเคชันได้อย่างรวดเร็วและมีประสิทธิภาพ นอกจากนี้ การเก็บข้อมูลแบบแยกตามฟิลด์ เช่น ภาพหลัก ภาพรอง และรายละเอียดสินค้า ทำให้ระบบสามารถจัดการข้อมูลได้อย่างยืดหยุ่นตามความต้องการของผู้ใช้


3. MQTT Protocol (RabbitMQ)

ในส่วนของระบบ Back-end สำหรับการสื่อสารระหว่าง Front-end กับหุ่นยนต์ Temi จะใช้ MQTT Protocol ซึ่งเป็นโปรโตคอลที่ถูกออกแบบมาเพื่อการสื่อสารที่รวดเร็วและมีประสิทธิภาพในระบบ IoT (Internet of Things) โดยในโครงการนี้เลือกใช้ RabbitMQ ซึ่งเป็นตัวกลางในการส่งข้อความ (Message Broker) ที่รองรับโปรโตคอล MQTT

RabbitMQ ทำหน้าที่เป็นตัวกลางในการรับ-ส่งข้อมูล โดยจะจัดการการส่งข้อความระหว่างส่วนต่าง ๆ ของระบบ เช่น การรับคำสั่งจาก Front-end แล้วส่งต่อไปยังหุ่นยนต์ Temi เพื่อให้หุ่นยนต์ทำงานตามคำสั่งนั้น ซึ่งช่วยให้การสื่อสารมีความเสถียรและรวดเร็ว

Message Queue คือตัวที่ทำหน้าที่ในการรับ Message และกระจาย Message ออกไปให้ปลายทาง

  • เรา (ผู้ส่ง) คือ Producer
  • ที่ทำการไปรษณีย์ คือ Queue
  • บุรุษไปรษณีย์ คือ Consumer

จากภาพที่แสดง เป็นการอธิบายกระบวนการทำงานของระบบควบคุมหุ่นยนต์ Temi ผ่านการสื่อสารด้วย MQTT Protocolโดยใช้ RabbitMQ เป็นตัวกลางสำหรับการส่งข้อมูลและคำสั่ง เช่น ผู้ใช้โต้ตอบกับเว็บแอปพลิเคชัน โดยส่งข้อมูลการป้อนของผู้ใช้ (ในกรณีนี้คือข้อมูลจากแป้นพิมพ์) ไปยัง API ซึ่งจะส่งข้อมูลนั้นไปยัง MQTT server และจากนั้น MQTT จะส่งคำสั่งไปยังหุ่นยนต์เพื่อทำการเคลื่อนที่ (MOVE) ตามคำสั่งของผู้ใช้

Message Queue ที่ใช้ในโครงงานมีดังนี้

robot_control_queue : ใช้สำหรับการควมคุม movement robot

robot_control_head_queue: ใช้สำหรับการควมคุม movement robot head

data_update_queue : ใช้สำหรับสั่งรีโหลดการโหลดข้อมูลฝั่ง Temi Andrioid app จาก database เมื่อมีการแก้ไขอัพเดตจากหน้าเว็บ


4. WSS (web socket Secure)

WSS (WebSocket Secure) เป็นโปรโตคอลที่ใช้สำหรับการสื่อสารแบบเรียลไทม์ระหว่างเซิร์ฟเวอร์และไคลเอนต์ (เช่น เว็บเบราว์เซอร์) โดยทำงานบนโปรโตคอล WebSocket ในการทำงานของระบบนี้ WSS ถูกใช้เพื่อสร้างการเชื่อมต่อแบบเรียลไทม์ระหว่างหุ่นยนต์ Temi กับเว็บแอปพลิเคชัน เพื่อให้ผู้ใช้งานสามารถดูข้อมูลวิดีโอหรือข้อมูลอื่น ๆ จากหุ่นยนต์ได้ทันที เช่น การดูภาพจากกล้องหุ่นยนต์แบบเรียลไทม์ หรือการรับข้อมูลสถานะต่าง ๆ ของหุ่นยนต์โดยไม่มีความล่าช้า

การดูภาพจากกล้องหุ่นยนต์แบบเรียลไทม์โดยใช้ WSS อันดับแรกต้องสร้าง WebSocket Server โดยใช้ Python และไลบรารี asyncio กับ websockets เพื่อส่งข้อมูลภาพวิดีโอแบบเรียลไทม์จากกล้องของคอมพิวเตอร์ไปยังไคลเอนต์ที่เชื่อมต่อผ่าน WebSocket โดยภาพจะถูกเข้ารหัสเป็น Base64 ก่อนส่งไปยังไคลเอนต์ ดังภาพ diagram ที่แสดงอยู่ด้านล่าง:

จากภาพ Diagram ข้างต้น แสดงการทำงานของระบบการถ่ายทอดวิดีโอแบบทางเดียว (One Way Video) โดยใช้ WebSocket Server ที่ทำงานอยู่บน Raspberry Pi ซึ่งเชื่อมต่อกับ Webcam เพื่อจับภาพวิดีโอแบบเรียลไทม์และส่งต่อไปยัง Front-end ผ่าน WebSocket โปรโตคอล

การทำงานของโค้ด:

การเข้ารหัสภาพ (Base64 Encoding)


def encode_frame(frame):
    _, buffer = cv2.imencode('.jpg', frame)  # เข้ารหัสภาพเป็น JPEG
    frame_bytes = base64.b64encode(buffer)    # แปลงภาพ JPEG เป็น Base64
    return frame_bytes.decode('utf-8')        # แปลง Base64 เป็น string

ฟังก์ชันนี้ใช้สำหรับแปลงเฟรมภาพที่ได้จากกล้องให้เป็น Base64 เพื่อให้สามารถส่งผ่าน WebSocket ได้ในรูปแบบของ string

การเข้ารหัสภาพ (Base64 Encoding)


def encode_frame(frame):
    _, buffer = cv2.imencode('.jpg', frame)  # เข้ารหัสภาพเป็น JPEG
    frame_bytes = base64.b64encode(buffer)    # แปลงภาพ JPEG เป็น Base64
    return frame_bytes.decode('utf-8')        # แปลง Base64 เป็น string

ฟังก์ชันนี้ใช้สำหรับแปลงเฟรมภาพที่ได้จากกล้องให้เป็น Base64 เพื่อให้สามารถส่งผ่าน WebSocket ได้ในรูปแบบของ string

WebSocket Handler

async def main():
    async with websockets.serve(stream_video, "0.0.0.0", 8765):
        print("WebSocket server started on ws://0.0.0.0:8765")
        await asyncio.Future()  # ทำงานต่อเนื่องไม่มีการหยุด
  • เมื่อมีการเชื่อมต่อจากไคลเอนต์ผ่าน WebSocket ฟังก์ชันนี้จะเปิดกล้องของคอมพิวเตอร์ (ใช้ cv2.VideoCapture(0)) และอ่านภาพจากกล้อง
  • หลังจากอ่านภาพได้แล้วจะเข้ารหัสภาพด้วย Base64 และส่งไปยังไคลเอนต์ที่เชื่อมต่ออยู่
  • มีการใช้ await asyncio.sleep(1 / 30) เพื่อควบคุมอัตราเฟรมให้อยู่ที่ประมาณ 30 FPS

การเริ่ม WebSocket Server

async def main():
    async with websockets.serve(stream_video, "0.0.0.0", 8765):
        print("WebSocket server started on ws://0.0.0.0:8765")
        await asyncio.Future()  # ทำงานต่อเนื่องไม่มีการหยุด
  • ส่วนนี้ทำหน้าที่เริ่มต้น WebSocket Server ที่ทำงานอยู่บน IP 0.0.0.0 และพอร์ต 8765 โดยเมื่อมีไคลเอนต์เชื่อมต่อเข้ามา ฟังก์ชัน stream_video จะถูกเรียกใช้เพื่อเริ่มส่งภาพวิดีโอ

โดยพอสร้าง WebSocket Server แล้วเราสามารถรับข้อมูลภาพจาก ip ได้โดยเราสามารถนำ ip นี้ไปใส่โค้ดในา่วนของ Front-end ได้เลย แต่ต้องมั่นใจว่า ip ของ WebSocket Server ต้องอยู่ในวงอินเตอร์เน็ตเดียวกัน


Temi Android App

แอปพลิเคชันนี้ถูกออกแบบมาเพื่อควบคุมหุ่นยนต์ Temi และดึงข้อมูลสินค้าจากเซิร์ฟเวอร์ โดยใช้เทคโนโลยีหลายอย่าง เช่น Retrofit สำหรับการสื่อสารกับ API, RabbitMQ สำหรับการส่งข้อความ และ Temi SDK ซึ่งเป็นชุดเครื่องมือพัฒนาซอฟต์แวร์ที่ใช้สำหรับควบคุมและสื่อสารกับหุ่นยนต์ Temi

รายละเอียดของไฟล์สำคัญและการทำงานของแอปพลิเคชันจะแบ่งออกเป็น 3 ส่วนหลัก คือ:

  1. การใช้งาน Temi Robot SDK: อธิบายการสื่อสารและควบคุมหุ่นยนต์ผ่าน Temi SDK
  2. Back-end: การจัดการข้อมูลและการเชื่อมต่อกับเซิร์ฟเวอร์ผ่าน RabbitMQ และ Retrofit
  3. Front-end หรือ UI: ส่วนที่เกี่ยวข้องกับการแสดงผลและการโต้ตอบกับผู้ใช้ในแอปพลิเคชัน

Temi Robot SDK

Temi Robot SDK เป็นชุดเครื่องมือพัฒนาซอฟต์แวร์ที่ใช้สำหรับควบคุมหุ่นยนต์ Temi โดย SDK นี้ช่วยให้นักพัฒนาสามารถสื่อสารกับหุ่นยนต์ Temi และควบคุมการทำงานต่าง ๆ เช่น การเคลื่อนที่ การปรับมุมกล้อง การควบคุมการเคลื่อนไหว และการโต้ตอบกับผู้ใช้ผ่านหน้าจอหุ่นยนต์

ฟังก์ชันที่สำคัญใน Temi SDK:

  1. การเคลื่อนที่ (Movement Control):
    • robot.skidJoy(x, y, false): ใช้สำหรับควบคุมการเคลื่อนที่ของหุ่นยนต์ Temi โดยค่า x ควบคุมการเคลื่อนที่ไปข้างหน้าหรือถอยหลัง และค่า y ควบคุมการเลี้ยวซ้ายหรือขวา
  2. การปรับมุมกล้อง (Head Control):
    • robot.tiltAngle(55, 1f): ใช้สำหรับปรับมุมกล้องของหุ่นยนต์ Temi โดยระบุองศาที่ต้องการ
  3. การซ่อนแถบด้านบน (UI Control):
    • robot.hideTopBar(): ใช้ในการซ่อนแถบเมนูด้านบนของ Temi เพื่อให้แสดงผลเต็มจอโดยไม่รบกวนการแสดงผลอื่น ๆ
  4. การตอบสนองต่อการพร้อมใช้งานของหุ่นยนต์ (OnRobotReadyListener):
    • ฟังก์ชันนี้จะถูกเรียกเมื่อหุ่นยนต์พร้อมทำงาน โดยสามารถตรวจสอบและเริ่มต้นการสื่อสารกับหุ่นยนต์ได้

การนำไปใช้งานในแอปพลิเคชัน:

นักพัฒนาสามารถใช้ Temi SDK เพื่อสร้างการโต้ตอบระหว่างผู้ใช้และหุ่นยนต์ เช่น การใช้หุ่นยนต์เพื่อนำทาง การแสดงวิดีโอ หรือการโต้ตอบกับสินค้าผ่าน QR Code ทั้งนี้ SDK ยังรองรับการปรับแต่งเพิ่มเติมเพื่อให้หุ่นยนต์สามารถปฏิบัติหน้าที่ต่าง ๆ ได้ตามความต้องการ

Back-end Android App

1. MainActivity.kt

วัตถุประสงค์MainActivity.kt เป็นหน้าจอหลักที่แสดงรายการสินค้าที่ดึงมาจากเซิร์ฟเวอร์ผ่าน RabbitMQ และแสดงผลในรูปแบบแนวนอนบนหน้าจอของ Temi robot

การทำงาน:

  1. เมื่อแอปเริ่มทำงาน MainActivity.kt จะเชื่อมต่อกับ RabbitMQService เพื่อรับและส่งข้อมูลสินค้าผ่าน RabbitMQ
  2. ข้อมูลสินค้าจะถูกแสดงใน RecyclerView ซึ่งแสดงรายการสินค้าในรูปแบบหน้าต่อหน้า (Horizontal Scroll) และสามารถเลื่อนดูได้
  3. หากมีการเคลื่อนไหวของหุ่นยนต์ Temi ผ่าน Temi SDK ระบบจะตรวจสอบความเร็วของหุ่นยนต์และเปลี่ยนไปที่หน้าจอวิดีโอ (VideoActivity) หากมีการเคลื่อนไหวเกิดขึ้น
  4. ระบบจะตรวจสอบสถานะการดึงข้อมูลสินค้า และแสดงหน้าจอ Loading หากกำลังดึงข้อมูล หรือหน้าจอ Error หากเกิดข้อผิดพลาดในการดึงข้อมูล

องค์ประกอบสำคัญ:

  • RecyclerView: ใช้สำหรับแสดงรายการสินค้าในรูปแบบแนวนอน โดยสามารถเลื่อนดูสินค้าได้
  • ProductPageAdapter: จัดการการแสดงผลรายการสินค้าบน RecyclerView โดยจัดกลุ่มสินค้าหน้าละ 3 รายการ
  • RabbitMQService: ใช้ในการเชื่อมต่อกับ RabbitMQ เพื่อรับ-ส่งข้อมูลสินค้าระหว่างเซิร์ฟเวอร์และแอป
  • Robot SDK (Temi): ใช้สำหรับควบคุมหุ่นยนต์ Temi โดยมีฟังก์ชันสำคัญดังนี้
    • OnRobotReadyListener: ใช้เพื่อตรวจสอบสถานะของหุ่นยนต์ Temi ว่าพร้อมใช้งานหรือไม่ หากหุ่นยนต์พร้อม ระบบจะเรียกใช้ข้อมูลเพิ่มเติมจาก Temi SDK
    • OnMovementVelocityChangedListener: ใช้ในการติดตามความเร็วของหุ่นยนต์ หากความเร็วเปลี่ยนแปลง ระบบจะตรวจสอบและส่งข้อมูลผ่าน RabbitMQ
    • OnMovementStatusChangedListener: ใช้ในการติดตามสถานะการเคลื่อนไหวของหุ่นยนต์ (เช่น กำลังเคลื่อนที่หรือหยุด)
    • การสั่งงานหุ่นยนต์ เช่น การปรับมุมกล้องด้วย robot.tiltAngle() และการซ่อนแถบด้านบนของหน้าจอด้วย robot.hideTopBar()
  • LoadingActivity: หน้าจอโหลดแสดงขณะระบบดึงข้อมูลสินค้า
  • ErrorActivity: หน้าจอที่แสดงเมื่อเกิดข้อผิดพลาดในการดึงข้อมูลสินค้า
  • BroadcastReceiver: ใช้สำหรับรับการอัปเดตข้อมูลสินค้าจาก Broadcast

ส่วนประกอบสำคัญของโค้ด: การตั้งค่า RecyclerView เพื่อแสดงผลรายการสินค้า:

private fun setupRecyclerView() {
    recyclerView = findViewById(R.id.recycler_view)
    recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
    productAdapter = ProductPageAdapter(this, listOf())
    recyclerView.adapter = productAdapter
    if (recyclerView.onFlingListener == null) {
        PagerSnapHelper().attachToRecyclerView(recyclerView)
    }
    fetchAndHandleProductsData()
}

การเชื่อมต่อกับ RabbitMQ Service:

private val rabbitMQServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        val binder = service as RabbitMQService.RabbitBinder
        rabbitMQService = binder.getService()
        isBoundRabbit = true
        rabbitMQService?.setMainActivity(this@MainActivity)
        Log.d("MainActivity", "RabbitMQService bound")
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        rabbitMQService = null
        isBoundRabbit = false
        Log.d("MainActivity", "RabbitMQService unbound")
    }
}

การเชื่อมต่อกับ Temi SDK และติดตามความเคลื่อนไหว:

robot = Robot.getInstance()
robot.addOnRobotReadyListener(this)
robot.addOnMovementVelocityChangedListener(this)
robot.addOnMovementStatusChangedListener(this)

การจัดการคำสั่งเคลื่อนไหวของหุ่นยนต์ Temi และเปลี่ยนหน้าจอเป็น VideoActivity:

override fun onMovementVelocityChanged(velocity: Float) {
    if (velocity > 0) {
        lifecycleScope.launch {
            delay(500L)
            startActivity(Intent(this@MainActivity, VideoActivity::class.java))
        }
    }

การเริ่มแสดงหน้าจอ LoadingActivity ขณะดึงข้อมูล:

private fun startLoadingScreen() {
    if (isLoadingScreenActive) return
    isLoadingScreenActive = true
    val intent = Intent(this, LoadingActivity::class.java)
    startActivity(intent)
}

การอัปเดตข้อมูลสินค้าเมื่อมีการดึงข้อมูลใหม่:

private val productUpdateReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        refreshProductList()
    }
}

2. Product.kt

วัตถุประสงค์Product.kt เป็นคลาสข้อมูลที่ใช้จัดเก็บรายละเอียดของสินค้า เช่น รหัสสินค้า ชื่อ ราคา และรายละเอียด รวมถึงภาพสินค้าที่แสดงบนหน้าจอของ Temi robot

การทำงาน:

  1. คลาส Product ใช้สำหรับเก็บข้อมูลของสินค้าแต่ละรายการ ประกอบด้วยข้อมูลพื้นฐาน เช่น รหัสสินค้า (id), ชื่อ (name), ราคา (price), รายละเอียด (detail) และภาพสินค้า (productImage)
  2. คลาส ProductImage ใช้เก็บข้อมูลภาพสินค้าในรูปแบบ String ซึ่งเป็นข้อมูลภาพที่อาจจะเป็น Base64 หรือ URL สำหรับการแสดงภาพสินค้าผ่านหน้าจอ

องค์ประกอบสำคัญ:

  • Product: เป็นคลาสหลักที่เก็บข้อมูลสินค้า ประกอบด้วย:
    • id: รหัสสินค้า (ชนิด Int)
    • name: ชื่อสินค้า (ชนิด String)
    • price: ราคาสินค้า (ชนิด String)
    • detail: รายละเอียดสินค้า (ชนิด String)
    • productImage: ภาพสินค้า (ชนิด ProductImage ซึ่งสามารถเป็น null ได้)
  • ProductImage: เป็นคลาสที่เก็บข้อมูลภาพสินค้าในรูปแบบ String (อาจเป็น Base64 หรือ URL)
data class Product(
    val id: Int,
    val name: String,
    val caption: String,
    val detail: String,
   val imageData: String? = null
)

3. ProductApi.kt

วัตถุประสงค์ProductApi.kt เป็นอินเทอร์เฟซสำหรับการเชื่อมต่อกับ API เพื่อดึงข้อมูลสินค้า รวมถึงรูปภาพสินค้าและรหัส QR จากเซิร์ฟเวอร์

การทำงาน:

  1. เมื่อแอปต้องการดึงข้อมูลสินค้าจากเซิร์ฟเวอร์ ProductApi.kt จะทำการเรียกใช้ API ต่างๆ ผ่าน Retrofit ซึ่งเป็นไลบรารีที่ใช้จัดการการส่งคำขอ HTTP ไปยังเซิร์ฟเวอร์
  2. API ที่ถูกเรียกใช้งานผ่าน ProductApi.kt จะดึงข้อมูลที่เกี่ยวข้องกับสินค้า เช่น ข้อมูลรายการสินค้า รูปภาพสินค้า หรือรหัส QR ของสินค้ามาแสดงบนหน้าจอของแอป
  3. ข้อมูลที่ได้รับจากคำขอจะถูกจัดการและส่งไปยังส่วนแสดงผลของแอปพลิเคชัน

องค์ประกอบสำคัญ:

  • Retrofit API: ใช้สำหรับดึงข้อมูลจากเซิร์ฟเวอร์ผ่านคำขอ HTTP
    • getProductsData(): ใช้ดึงข้อมูลสินค้าทั้งหมดจาก API ที่ /api/products/data โดยส่งคืนรายการสินค้าในรูปแบบ List<Product>
    • getQrCodeImage(productId: Int): ใช้ดึงภาพ QR Code ของสินค้าผ่าน productId จาก API ที่ /api/qrCodeImage/{id} โดยส่งคืนรูปภาพในรูปแบบ ResponseBody
    • getProductImage(productId: Int): ใช้ดึงภาพของสินค้า โดยใช้ productId จาก API ที่ /api/productImage/{id} โดยส่งคืนรูปภาพในรูปแบบ ResponseBody
    • getProductDetail(productId: Int): ใช้ดึงรายละเอียดเฉพาะของสินค้าผ่าน productId จาก API ที่ /api/products/detail/{id} โดยส่งคืนข้อมูลสินค้าในรูปแบบ Product
interface ProductApi {
    @GET("/api/products/data")
    fun getProductsData(): Call<List<Product>>
    @GET("/api/qrCodeImage/{id}")
    fun getQrCodeImage(@Path("id") productId: Int): Call<ResponseBody>
    @GET("/api/productImage/{id}")
    fun getProductImage(@Path("id") productId: Int): Call<ResponseBody>
    @GET("/api/products/detail/{id}")
    fun getProductDetail(@Path("id") productId: Int): Call<Product>
}

4. ProductRepository.kt

วัตถุประสงค์ProductRepository.kt เป็นคลาสที่ใช้จัดการการดึงข้อมูลสินค้า รูปภาพสินค้า และรายละเอียดของสินค้าจากเซิร์ฟเวอร์ผ่านการเชื่อมต่อ Retrofit API และนำข้อมูลเหล่านี้มาใช้ในแอปพลิเคชัน

การทำงาน:

  1. ProductRepository ทำหน้าที่เป็นตัวกลางระหว่างแอปพลิเคชันและ API โดยใช้ RetrofitClient เพื่อเรียกใช้ API ที่เกี่ยวข้องกับสินค้า เช่น การดึงข้อมูลสินค้าทั้งหมด (getProductsData()), รายละเอียดสินค้า (getProductDetails()), รูปภาพสินค้า (getProductImage()), และภาพ QR Code (getQrCodeImage())
  2. แต่ละฟังก์ชันจะส่งคำขอไปยังเซิร์ฟเวอร์ผ่าน API ที่กำหนด และใช้ Callback ในการรับข้อมูลที่ดึงมา หากคำขอสำเร็จ ข้อมูลจะถูกส่งกลับไปยัง UI ผ่านการเรียกใช้ callback() ที่ส่งข้อมูลไปยังส่วนที่เรียกใช้
  3. หากคำขอล้มเหลว (onFailure), callback(null) จะถูกเรียกใช้เพื่อแจ้งเตือนว่ามีการเกิดข้อผิดพลาดในการดึงข้อมูล

องค์ประกอบสำคัญ:

  • RetrofitClient: ใช้ในการเรียกใช้งาน API ผ่าน Retrofit โดย productApi ถูกกำหนดให้ใช้ ProductApi ซึ่งรวมฟังก์ชันที่เชื่อมต่อกับ API
    • productApi.getProductsData(): เรียกข้อมูลสินค้าจาก API ในรูปแบบรายการ (List<Product>)
    • productApi.getProductDetail(productId: Int): ดึงข้อมูลรายละเอียดสินค้าเฉพาะจาก API
    • productApi.getProductImage(productId: Int): ดึงข้อมูลรูปภาพสินค้าจาก API
    • productApi.getQrCodeImage(productId: Int): ดึงข้อมูลภาพ QR Code ของสินค้าจาก API
  • Callback: ใช้เพื่อรอผลลัพธ์จาก API หากคำขอสำเร็จหรือเกิดข้อผิดพลาด ฟังก์ชัน onResponse() หรือ onFailure()จะถูกเรียกใช้ตามลำดับเพื่อจัดการผลลัพธ์นั้น
    • onResponse(): รับข้อมูลเมื่อคำขอสำเร็จ
    • onFailure(): รับการแจ้งเตือนเมื่อเกิดข้อผิดพลาดในการทำคำขอ

ส่วนประกอบสำคัญของโค้ด:

การดึงข้อมูลสินค้าทั้งหมดและจัดการเมื่อคำขอสำเร็จ:

fun getProductsData(callback: (List<Product>?) -> Unit) {
    val call = productApi.getProductsData()
    call.enqueue(object : Callback<List<Product>> {
        override fun onResponse(call: Call<List<Product>>, response: Response<List<Product>>) {
            if (response.isSuccessful) {
                val products = response.body()
                productList.clear()
                products?.let { productList.addAll(it) }
                callback(products)
            } else {
                callback(null)
            }
        }

        override fun onFailure(call: Call<List<Product>>, t: Throwable) {
            callback(null)
        }
    })
}

การดึงรูปภาพสินค้า:

fun getProductImage(productId: Int, callback: (ResponseBody?) -> Unit) {
    val call = productApi.getProductImage(productId)
    call.enqueue(object : Callback<ResponseBody> {
        override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
            if (response.isSuccessful) {
                callback(response.body())
            } else {
                callback(null)
            }
        }

        override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
            callback(null)
        }
    })
}

5. RetrofitClient.kt

วัตถุประสงค์: ไฟล์นี้ทำหน้าที่ตั้งค่าและจัดการการเชื่อมต่อกับเซิร์ฟเวอร์ผ่าน Retrofit

การทำงาน:

  1. สร้าง Singleton Instance ของ Retrofit สำหรับเชื่อมต่อกับ Base URL ของเซิร์ฟเวอร์
  2. ใช้ GsonConverterFactory เพื่อแปลงข้อมูล JSON เป็นออบเจ็กต์ใน Kotlin
object RetrofitClient {
    private const val BASE_URL = "https://your-api-url.com/"

    val apiService: ProductApi by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ProductApi::class.java)
    }
}

6. RabbitMQConnection.kt

วัตถุประสงค์RabbitMQConnection.kt เป็นคลาสที่ใช้ในการเชื่อมต่อกับ RabbitMQ เซิร์ฟเวอร์เพื่อส่งและรับข้อมูลผ่านโปรโตคอล AMQP โดยใช้ RabbitMQ Java Client

การทำงาน:

  1. คลาส RabbitMQConnection จะทำหน้าที่ในการจัดการการเชื่อมต่อกับ RabbitMQ เซิร์ฟเวอร์ โดยรับข้อมูลเช่น host, port, username และ password
  2. ฟังก์ชัน connect() จะสร้างการเชื่อมต่อกับ RabbitMQ และเปิด channel สำหรับการสื่อสาร ข้อมูลเหล่านี้จะถูกใช้เพื่อส่งและรับข้อมูลจากคิว (queue) ของ RabbitMQ
  3. ฟังก์ชัน disconnect() จะทำการปิดการเชื่อมต่อกับ RabbitMQ อย่างเหมาะสมเพื่อป้องกันปัญหาการเชื่อมต่อค้างหลังจากเลิกใช้งาน

องค์ประกอบสำคัญ:

  • ConnectionFactory: ใช้ในการสร้างการเชื่อมต่อกับ RabbitMQ โดยการกำหนดค่าพื้นฐาน เช่น host, port, username, password, timeout และ heartbeat
    • this.host: กำหนดโฮสต์ของ RabbitMQ (ที่อยู่เซิร์ฟเวอร์)
    • this.port: กำหนดพอร์ตที่ใช้สำหรับเชื่อมต่อ RabbitMQ
    • this.username และ this.password: กำหนดข้อมูลการเข้าสู่ระบบ
    • this.connectionTimeout = 30000: กำหนดค่า timeout ในการเชื่อมต่อ (30 วินาที)
    • this.requestedHeartbeat = 10: กำหนดค่าช่วงเวลาสำหรับการส่ง heartbeat (10 วินาที)
  • Channel: ใช้สำหรับสร้างช่องทางการสื่อสารกับ RabbitMQ เมื่อการเชื่อมต่อสำเร็จ จะใช้ช่องทางนี้ในการส่งหรือรับข้อมูล
    • connection?.createChannel(): สร้าง channel หลังจากที่การเชื่อมต่อเสร็จสิ้น
    • channel?.takeIf { it.isOpen }?.close(): ปิด channel หากเปิดอยู่
val factory = ConnectionFactory().apply {
    this.host = [email protected]
    this.port = [email protected]
    this.username = [email protected]
    this.password = [email protected]
    this.connectionTimeout = 30000
    this.requestedHeartbeat = 10
}
connection = factory.newConnection()
channel = connection?.createChannel()

7. RabbitMQClient.kt

วัตถุประสงค์RabbitMQClient.kt เป็นคลาสที่ใช้สำหรับการรับข้อความ (message) จาก RabbitMQ queue โดยกำหนดชื่อคิวและตัวจัดการข้อความ (message handlers) สำหรับการประมวลผลข้อมูลที่ถูกส่งมาจากเซิร์ฟเวอร์

การทำงาน:

  1. คลาส RabbitMQClient จะรับการเชื่อมต่อจากคลาส RabbitMQConnection เพื่อสร้างช่องทางการสื่อสารกับ RabbitMQ เซิร์ฟเวอร์
  2. เมื่อเชื่อมต่อสำเร็จ ฟังก์ชัน startConsuming() จะทำการกำหนดค่า consumer สำหรับคิวแต่ละตัว (queue) และเริ่มรับข้อมูล
  3. ข้อมูลที่รับจากคิวจะถูกส่งไปยังตัวจัดการข้อความ (message handler) ที่ระบุ โดยฟังก์ชัน handleRabbitMqMessage()จะทำการรันโค้ดใน main thread เพื่อให้สามารถจัดการ UI ได้หากจำเป็น
  4. ฟังก์ชัน stopConsuming() จะทำการยกเลิกการเชื่อมต่อจาก RabbitMQ และหยุดการรับข้อความเมื่อเรียกใช้งาน

องค์ประกอบสำคัญ:

  • RabbitMQConnection: ใช้ในการเชื่อมต่อกับ RabbitMQ เซิร์ฟเวอร์ โดยใช้ connection และ channel ที่สร้างขึ้นเพื่อเริ่มรับข้อความจากคิว
    • connection.connect(): สร้างการเชื่อมต่อและเปิดช่องทางรับข้อความ
    • connection.disconnect(): ยกเลิกการเชื่อมต่อเมื่อหยุดการทำงาน
  • ConnectionListener: อินเทอร์เฟซที่ใช้ตรวจสอบสถานะการเชื่อมต่อ RabbitMQ เมื่อเชื่อมต่อหรือถูกยกเลิกการเชื่อมต่อ
    • onConnected(): เรียกใช้เมื่อการเชื่อมต่อสำเร็จ
    • onDisconnected(): เรียกใช้เมื่อการเชื่อมต่อล้มเหลวหรือถูกตัดการเชื่อมต่อ
  • DefaultConsumer: ใช้สำหรับรับข้อความจากคิว RabbitMQ ที่ระบุผ่านชื่อคิว (queueName) และส่งต่อให้กับตัวจัดการข้อความที่ถูกกำหนดไว้
    • handleDelivery(): รับข้อความจากคิวและแปลงข้อมูลให้เป็น String เพื่อส่งไปยังตัวจัดการข้อความ
  • Handler และ Looper: ใช้เพื่อให้แน่ใจว่าการประมวลผลข้อความจาก RabbitMQ ถูกดำเนินการใน main thread เพื่อความสอดคล้องในการจัดการ UI

ส่วนประกอบสำคัญของโค้ด:

การกำหนดค่า consumer สำหรับแต่ละคิว:

val consumer = object : DefaultConsumer(channel) {
    override fun handleDelivery(
        consumerTag: String?,
        envelope: Envelope?,
        properties: AMQP.BasicProperties?,
        body: ByteArray?
    ) {
        val message = String(body ?: ByteArray(0), StandardCharsets.UTF_8)
        handleRabbitMqMessage(queueName, message)
    }
}
channel.basicConsume(queueName, true, consumer)

การประมวลผลข้อความใน main thread เพื่อความสอดคล้องในการจัดการ UI:

private fun handleRabbitMqMessage(queueName: String, message: String) {
    val handler = Handler(Looper.getMainLooper())
    handler.post {
        queues.find { it.first == queueName }?.second?.invoke(message)
    }
}

8. RabbitMQService.kt

วัตถุประสงค์RabbitMQService.kt เป็น Android Service ที่จัดการการเชื่อมต่อและการสื่อสารกับ RabbitMQ เซิร์ฟเวอร์ โดยรับและส่งข้อความไปยังคิวต่าง ๆ และควบคุมหุ่นยนต์ Temi ผ่านการใช้ RobotController

การทำงาน:

  1. เมื่อเริ่มทำงาน RabbitMQService จะสร้างการเชื่อมต่อกับ RabbitMQ เซิร์ฟเวอร์โดยใช้ RabbitMQConnection เพื่อจัดการการเชื่อมต่อ
  2. บริการนี้จะฟังข้อความจากคิวที่กำหนดใน RabbitMQ เช่น "robot_control_queue""store_update_queue", และ "robot_control_head_queue" และจัดการกับข้อความที่รับเข้ามาโดยการส่งไปยังฟังก์ชันจัดการที่เหมาะสม
  3. สามารถส่งข้อความไปยังคิว RabbitMQ ผ่าน sendMessage() และยังสามารถรับคำสั่งจาก MainActivity เพื่อสั่งให้หุ่นยนต์ Temi ทำงานได้
  4. หากการเชื่อมต่อกับ RabbitMQ ล้มเหลว ระบบจะพยายามเชื่อมต่อใหม่โดยอัตโนมัติผ่านฟังก์ชัน reconnect()
  5. เมื่อบริการถูกหยุด (onDestroy) การเชื่อมต่อ RabbitMQ จะถูกยกเลิกอย่างถูกต้อง

องค์ประกอบสำคัญ:

  • RabbitMQConnection: ใช้สำหรับสร้างการเชื่อมต่อกับ RabbitMQ เซิร์ฟเวอร์โดยกำหนด host, port, username, และ password
    • rabbitMQConnection = RabbitMQConnection(...): สร้างการเชื่อมต่อกับเซิร์ฟเวอร์ RabbitMQ เมื่อเริ่มใช้งาน
  • RabbitMQClient: ใช้เพื่อรับข้อความจาก RabbitMQ คิวและส่งไปยังตัวจัดการข้อความที่กำหนด เช่น การควบคุมหุ่นยนต์ หรือการอัปเดตข้อมูลสินค้า
    • rabbitMQClient.startConsuming(): เริ่มรับข้อความจากคิวที่กำหนด
    • rabbitMQClient.setConnectionListener(): กำหนดการฟังการเชื่อมต่อ หากเชื่อมต่อหรือถูกตัดการเชื่อมต่อ
  • RobotController: ใช้เพื่อควบคุมการทำงานของหุ่นยนต์ Temi โดยรับคำสั่งจาก RabbitMQ และจัดการผ่านฟังก์ชัน handleRabbitMqControllMessage() และ handleRabbitMqHeadControllMessage()
    • robotController?.handleRabbitMqControllMessage(): ควบคุมการเคลื่อนไหวของหุ่นยนต์ Temi ผ่านคำสั่งที่รับจาก RabbitMQ

ส่วนประกอบสำคัญของโค้ด:

การสร้างการเชื่อมต่อ RabbitMQ:

rabbitMQConnection = RabbitMQConnection(
    host = Config.rabbitMQHost,
    port = Config.rabbitMQPort,
    username = Config.rabbitMQUsername,
    password = Config.rabbitMQPassword
)

การกำหนดการเชื่อมต่อ RabbitMQClient เพื่อเริ่มรับข้อความ:

rabbitMQClient = RabbitMQClient(
    connection = rabbitMQConnection,
    queues = queueHandlers
)
rabbitMQClient.startConsuming()

การจัดการกับข้อความที่รับจาก RabbitMQ:

private fun reconnect() {
    serviceScope.launch {
        rabbitMQClient.stopConsuming()
        rabbitMQClient.startConsuming()
    }
}

การส่งข้อความไปยังคิว RabbitMQ:

fun sendMessage(queueName: String, message: String) {
    serviceScope.launch {
        try {
            rabbitMQSender.sendMessage(queueName, message)
            Log.d("RabbitMQService", "Message sent to $queueName: $message")
        } catch (e: Exception) {
            Log.e("RabbitMQService", "Error sending message: ${e.message}", e)
        }
    }
}

9. Config.kt

วัตถุประสงค์Config.kt เป็นคลาสที่เก็บค่าคอนฟิกูเรชัน (configuration) สำหรับการเชื่อมต่อกับ API และ RabbitMQ เซิร์ฟเวอร์ โดยใช้ Object เพื่อให้ค่าคอนฟิกสามารถเข้าถึงได้ทั่วทั้งแอปพลิเคชัน

การทำงาน:

  1. คลาส Config ทำหน้าที่เก็บข้อมูลคอนฟิกูเรชันที่ใช้สำหรับการเชื่อมต่อกับ API และ RabbitMQ เช่น ที่อยู่ของเซิร์ฟเวอร์ (apiBaseUrlrabbitMQHost), พอร์ต (rabbitMQPort), และข้อมูลการล็อกอิน (rabbitMQUsernamerabbitMQPassword)
  2. การใช้ Config ในรูปแบบของ object ช่วยให้สามารถเข้าถึงค่าคอนฟิกจากที่ใดก็ได้ในแอปพลิเคชันโดยไม่ต้องสร้าง instance ใหม่ ทำให้สามารถใช้งานได้อย่างง่ายดายและสะดวก

องค์ประกอบสำคัญ:

  • apiBaseUrl: URL สำหรับเชื่อมต่อกับ API ของแอปพลิเคชัน เช่น สำหรับดึงข้อมูลสินค้าและรายละเอียดสินค้า
    • "<http://10.9.157.34:3002>": URL ของ API ที่ใช้
  • rabbitMQHost: ที่อยู่ของเซิร์ฟเวอร์ RabbitMQ
    • "10.9.157.34": ที่อยู่ IP ของเซิร์ฟเวอร์ RabbitMQ
  • rabbitMQPort: พอร์ตที่ใช้เชื่อมต่อกับ RabbitMQ
    • 5672: พอร์ตเริ่มต้นสำหรับการเชื่อมต่อ RabbitMQ
  • rabbitMQUsername: ชื่อผู้ใช้สำหรับการเข้าสู่ระบบ RabbitMQ
    • "admin": ชื่อผู้ใช้เริ่มต้น
  • rabbitMQPassword: รหัสผ่านสำหรับการเข้าสู่ระบบ RabbitMQ
    • "123456": รหัสผ่านสำหรับการเข้าสู่ระบบ

ส่วนประกอบสำคัญของโค้ด:

object Config {
    var apiBaseUrl: String = "http://10.9.157.34:3002"  // API URL configuration

    var rabbitMQHost: String = "10.9.157.34"            // RabbitMQ host
    var rabbitMQPort: Int = 5672                        // RabbitMQ port
    var rabbitMQUsername: String = "admin"              // RabbitMQ username
    var rabbitMQPassword: String = "123456"             // RabbitMQ password
}

ค่าคอนฟิกเหล่านี้ถูกกำหนดไว้ในรูปแบบตัวแปรที่สามารถปรับเปลี่ยนได้ ทำให้สามารถตั้งค่าที่แตกต่างกันในแต่ละสภาพแวดล้อม เช่น การทดสอบหรือการใช้งานจริงได้อย่างง่ายดาย


10. RobotController.kt

วัตถุประสงค์RobotController.kt เป็นคลาสที่ใช้ในการควบคุมการเคลื่อนไหวของหุ่นยนต์ Temi ผ่านคำสั่งที่ได้รับจาก RabbitMQ โดยใช้ Robot SDK เพื่อสั่งการให้หุ่นยนต์เคลื่อนที่หรือปรับมุมกล้อง

การทำงาน:

  1. เมื่อแอปพลิเคชันได้รับข้อความจาก RabbitMQ ผ่าน RabbitMQService คำสั่งเหล่านั้นจะถูกส่งมายัง RobotController เพื่อทำงานควบคุมหุ่นยนต์ตามคำสั่ง เช่น การเคลื่อนที่ไปข้างหน้า ถอยหลัง เลี้ยวซ้าย เลี้ยวขวา หรือหยุดการเคลื่อนไหว
  2. ฟังก์ชัน handleRabbitMqControllMessage() จัดการคำสั่งที่เกี่ยวข้องกับการเคลื่อนไหวของหุ่นยนต์ เช่น การเดินหน้า ถอยหลัง และการหยุด
  3. ฟังก์ชัน handleRabbitMqHeadControllMessage() จัดการคำสั่งที่เกี่ยวข้องกับการปรับมุมกล้องของหุ่นยนต์ เช่น การปรับมุมกล้องขึ้น (HEAD_UP) หรือมุมกล้องลง (HEAD_DOWN)
  4. ใช้ controlRobotMovement(x: Float, y: Float) ในการสั่งการให้หุ่นยนต์เคลื่อนที่ไปในทิศทางที่ระบุ โดยควบคุมการเคลื่อนที่ด้วยพารามิเตอร์ x (เดินหน้า/ถอยหลัง) และ y (เลี้ยวซ้าย/เลี้ยวขวา)
  5. ฟังก์ชัน stopMovement() จะหยุดการเคลื่อนไหวของหุ่นยนต์ทันทีเมื่อได้รับคำสั่งหยุด

องค์ประกอบสำคัญ:

  • Robot SDK: ใช้ในการควบคุมหุ่นยนต์ Temi ผ่านคำสั่งต่างๆ
    • robot.skidJoy(x, y, false): สั่งให้หุ่นยนต์เคลื่อนที่ตามค่าพารามิเตอร์ x และ y
    • robot.tiltAngle(55, 1f): ปรับมุมกล้องของหุ่นยนต์
    • robot.stopMovement(): หยุดการเคลื่อนไหวของหุ่นยนต์ทันที
  • Handler และ Looper: ใช้สำหรับการทำงานแบบ asynchronous โดยทำงานบน main thread เพื่อให้สามารถจัดการการสั่งงานหุ่นยนต์ได้อย่างถูกต้อง
  • MainActivity: ใช้เป็น context เพื่อเปิดใช้งานกิจกรรมต่างๆ ในแอป เช่น การเปลี่ยนไปหน้าจอหลักหรือหน้าจอวิดีโอ

ส่วนประกอบสำคัญของโค้ด:

การจัดการคำสั่งการเคลื่อนที่ของหุ่นยนต์:

fun handleRabbitMqControllMessage(message: String) {
    handler.post {
        when (message) {
            STOP_COMMAND, STOP_COMMAND_ALT1, STOP_COMMAND_ALT2 -> {
                stopMovement()  // Instantly stop the robot
                return@post
            }
            MOVE_FORWARD ->  x = if (y != 0f) 0.3f else 0.4f
            MOVE_BACKWARD ->  x = if (y != 0f) -0.5f else -0.6f
            MOVE_LEFT -> y = if (x < 0) -1f else 1f
            MOVE_RIGHT -> y = if (x < 0) 1f else -1f
            else -> {
                Log.e("RobotController", "Unknown control command received: $message")
                return@post
            }
        }
        controlRobotMovement(x, y)
        mainActivity.resetInactivityTimeout()
        Log.d("RobotController", "Controlling movement after command: $message")
    }
}

การหยุดการเคลื่อนไหวของหุ่นยนต์:

private fun stopMovement() {
    robot.stopMovement()
    x = 0f
    y = 0f
    Log.d("RobotController", "Robot fully stopped")
}

การจัดการการปรับมุมกล้องของหุ่นยนต์:

fun handleRabbitMqHeadControllMessage(message: String) {
    handler.post {
        when (message) {
            HEAD_UP -> {
                robot.tiltAngle(55, 1f)
                changeToMainMenu()
                Log.d("RobotController", "Tilted head up")
            }
            HEAD_DOWN -> {
                robot.tiltAngle(0, 1f)
                changeTemiFace()
                Log.d("RobotController", "Tilted head down")
            }
            else -> {
                Log.e("RobotController", "Unknown head control command received: $message")
            }
        }
    }
}

Front-end Android App (UI)

การสร้าง UI สำหรับแอป Android เริ่มต้นจากการออกแบบหน้าตา UI ในโปรแกรม Android Studio ซึ่งเป็นเครื่องมือที่ช่วยนักพัฒนาออกแบบและจัดวางองค์ประกอบของ UI ได้ง่ายขึ้น ดังภาพตัวอย่างด้านล่าง:

หลังจากที่เราออกแบบ UI เสร็จแล้ว ขั้นตอนต่อไปคือการเขียนโค้ดเพื่อเชื่อมโยง UI ที่ออกแบบไว้กับการทำงานของแอปพลิเคชัน โดยในโครงการนี้ UI จะประกอบไปด้วยองค์ประกอบดังต่อไปนี้:

1. ErrorActivity.kt

วัตถุประสงค์ErrorActivity.kt แสดงหน้าจอเมื่อเกิดข้อผิดพลาด โดยมีปุ่ม Retry ให้ผู้ใช้กดเพื่อกลับไปที่ MainActivityและลองโหลดข้อมูลใหม่

การทำงาน:

  1. ซ่อน Action Bar และทำให้หน้าจอเต็มจอ
  2. ใช้ ViewBinding เชื่อมต่อ UI กับโค้ดผ่าน ActivityErrorBinding
  3. ปุ่ม Retry เรียกใช้ฟังก์ชัน retryMainActivity() เพื่อกลับไปที่ MainActivity พร้อมเอฟเฟกต์เฟดหน้าจอ

หน้าโชว์ว่าการเชื่อมต่อมีปัญหา

ส่วนประกอบสำคัญของโค้ด:

ซ่อน Action Bar และทำให้เต็มจอ:

supportActionBar?.hide()
Utils.hideSystemBars(window)

เชื่อมต่อ UI ผ่าน ViewBinding:

binding = ActivityErrorBinding.inflate(layoutInflater)
setContentView(binding.root)

ฟังก์ชันเปิด MainActivity ใหม่:

private fun retryMainActivity() {
    val intent = Intent(this, MainActivity::class.java).apply {
        addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
    }
    startActivity(intent)
    overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
    finish()
}

2. LoadingActivity.kt

วัตถุประสงค์LoadingActivity.kt เป็นหน้าจอแสดงผลระหว่างที่แอปพลิเคชันกำลังโหลดข้อมูล โดยทำให้หน้าจอแสดงผลแบบเต็มจอ (Full Screen) และซ่อนแถบสถานะและแถบนำทาง

การทำงาน:

  1. เมื่อหน้าจอโหลดเริ่มขึ้น (onCreate) แอปจะซ่อน Action Bar และทำให้แอปเข้าสู่โหมดเต็มจอ
  2. ใช้ Handler เพื่อปิดหน้าจอโหลดโดยอัตโนมัติหลังจาก 2 วินาทีผ่านฟังก์ชัน finishLoadingScreen()
  3. ฟังก์ชัน hideSystemBars() จะซ่อนแถบสถานะและแถบนำทางเพื่อประสบการณ์เต็มจอ
  4. เมื่อหน้าจอถูกทำลาย (onDestroy), การเรียกใช้ removeCallbacksAndMessages จะลบ Callback ที่ค้างอยู่

หน้าโหลดข้อมูล

ส่วนประกอบสำคัญของโค้ด:

การซ่อน Action Bar และทำให้แอปเป็น Full Screen:

supportActionBar?.hide()
window.setFlags(
    WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN
)

การปิดหน้าจอโหลดหลังจาก 2 วินาที:

handler.postDelayed({ finishLoadingScreen() }, 2000)

ฟังก์ชันสำหรับซ่อนแถบสถานะและแถบนำทาง:

private fun hideSystemBars() {
    window.insetsController?.apply {
        hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
    }
}

ลบ Callback เมื่อหน้าจอถูกทำลาย:

override fun onDestroy() {
    handler.removeCallbacksAndMessages(null)
}

3. ProductAdapter.kt

วัตถุประสงค์ProductPageAdapter.kt เป็น Adapter ที่ใช้สำหรับการแสดงข้อมูลผลิตภัณฑ์ใน RecyclerView โดยแต่ละหน้าจะมีสินค้าหลายรายการที่สามารถแสดงได้ เช่น ชื่อ ราคา และรูปภาพ พร้อมปุ่มสำหรับแสดง QR Code ของแต่ละสินค้า

การทำงาน:

  1. ProductPageAdapter เป็นคลาสที่ใช้สำหรับแสดงข้อมูลผลิตภัณฑ์เป็นหน้าๆ ใน RecyclerView โดยแต่ละหน้าอาจมีสินค้าหลายรายการ
  2. ข้อมูลผลิตภัณฑ์จะถูกดึงมาจาก ProductRepository และแสดงในรายการสินค้าโดยแต่ละสินค้าในหน้าเดียวกันจะมีชื่อสินค้า ราคา และรูปภาพ
  3. หากไม่มีข้อมูลสินค้าที่จะแสดง จะมีข้อความ “No Data” แสดงแทน
  4. ปุ่มสำหรับแสดง QR Code จะนำผู้ใช้ไปยังหน้าจอ QRCodeActivity เมื่อกดปุ่ม

องค์ประกอบสำคัญ:

  • ViewBinding: ใช้ในการผูกข้อมูลของสินค้าแต่ละรายการกับ UI เช่น TextViewImageView, และปุ่ม Button
    • มีการสร้าง ProductViews เพื่อรวมองค์ประกอบ UI แต่ละตัวของสินค้าให้สะดวกในการผูกข้อมูล
  • ProductRepository: ใช้ในการดึงข้อมูลสินค้าและรูปภาพผ่านการเชื่อมต่อกับ API
    • productRepository.getProductImage(): ใช้เพื่อดึงรูปภาพของสินค้าแต่ละรายการจาก API
  • Intent: ใช้ในการเปิดหน้าจอ QRCodeActivity เพื่อแสดง QR Code ของสินค้านั้นๆ

ตัวอย่าง หน้าการเลือกซื้อสินค้า

ตัวอย่าง หน้าการเลือกอ่านข้อมูลประชาสัมพันธ์

ส่วนประกอบสำคัญของโค้ด:

การสร้าง View สำหรับแต่ละหน้าใน RecyclerView:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item_product, parent, false)
    return PageViewHolder(view)
}

การผูกข้อมูลสินค้าเข้ากับ UI:

fun bindProducts(products: List<Product?>) {
    hideAllProducts()
    if (products.isEmpty()) {
        showNoDataMessage()
    } else {
        products.forEachIndexed { index, product ->
            bindProduct(product, productViews[index])
        }
    }
}

การดึงรูปภาพของสินค้า:

private fun fetchProductImage(productId: Int, imageView: ImageView) {
    productRepository.getProductImage(productId) { responseBody ->
        if (responseBody != null) {
            try {
                val inputStream = responseBody.byteStream()
                val bitmap = BitmapFactory.decodeStream(inputStream)
                imageView.setImageBitmap(bitmap)
                inputStream.close() // ปิดการใช้งาน input stream
            } catch (e: Exception) {
                Log.e("PageViewHolder", "Error decoding product image: ${e.localizedMessage}")
                imageView.setImageResource(R.drawable.placeholder)
            }
        } else {
            Log.e("PageViewHolder", "Failed to fetch image for product ID: $productId")
            imageView.setImageResource(R.drawable.placeholder)
        }
    }
}

การเปิดหน้าจอ QRCodeActivity เพื่อแสดง QR Code ของสินค้า:

private fun showQRCode(productId: Int) {
    val intent = Intent(context, QRCodeActivity::class.java).apply {
        putExtra("PRODUCT_ID", productId)
    }
    context.startActivity(intent)
}

4. QrCodeActivity.kt

วัตถุประสงค์QRCodeActivity.kt เป็นหน้าจอที่ใช้แสดงภาพ QR Code ของผลิตภัณฑ์และรายละเอียดสินค้าเมื่อผู้ใช้เลือกสินค้าจากรายการ โดยใช้ Robot SDK ของ Temi เพื่อควบคุมและแสดงข้อมูลบนหน้าจอหุ่นยนต์

การทำงาน:

  1. เมื่อหน้าจอเปิดขึ้น (onCreate), ระบบจะซ่อนแถบ Action Bar และแสดงหน้าจอเต็มจอโดยใช้ Utils.hideSystemBars() รวมถึงซ่อนแถบด้านบนของ Temi (robot.hideTopBar()).
  2. ดึง productId จาก Intent ที่ส่งมาจากหน้าจอก่อนหน้า เพื่อใช้ในการเรียกข้อมูล QR Code และรายละเอียดสินค้าผ่าน ProductRepository.
  3. ภาพ QR Code จะถูกดึงจาก API และแสดงใน ImageView หากไม่สามารถดึงภาพได้ จะมีการแสดงภาพ placeholder แทน.
  4. รายละเอียดสินค้าจะถูกแสดงใน TextView หากดึงข้อมูลสำเร็จ.
  5. เมื่อผู้ใช้กดปุ่มปิด (onCloseButtonClick), หน้าจอจะถูกปิดพร้อมกับตั้งค่าผลลัพธ์ (setResult(RESULT_OK)).

องค์ประกอบสำคัญ:

  • Temi Robot SDK: ใช้เพื่อควบคุมและติดตามสถานะของหุ่นยนต์ Temi
    • robot.onStart(activityInfo): ใช้สำหรับเตรียมการทำงานของหุ่นยนต์ Temi เมื่อพร้อมใช้งาน
    • robot.hideTopBar(): ใช้สำหรับซ่อนแถบด้านบนของหุ่นยนต์เพื่อประสบการณ์การใช้งานที่ดียิ่งขึ้น
  • ProductRepository: ใช้เพื่อดึงข้อมูล QR Code และรายละเอียดสินค้าจากเซิร์ฟเวอร์
    • getQrCodeImage(productId): ดึงภาพ QR Code สำหรับสินค้าที่เลือก
    • getProductDetails(productId): ดึงรายละเอียดสินค้าเพื่อนำมาแสดงใน TextView
  • Intent: ใช้สำหรับส่งข้อมูล productId จากหน้าก่อนหน้าเพื่อระบุตัวสินค้าที่ต้องการแสดง

ตัวอย่าง หน้าการแสดงรายละเอียดการขายสินค้า

ตัวอย่าง หน้าการแสดงรายละเอียดประชาสัมพันธ์

ส่วนประกอบสำคัญของโค้ด:

การดึงและแสดงภาพ QR Code:

private fun fetchQrCodeImage(productId: Int) {
    productRepository.getQrCodeImage(productId) { responseBody ->
        if (responseBody != null) {
            var inputStream = responseBody.byteStream()
            try {
                val bitmap = BitmapFactory.decodeStream(inputStream)
                qrCodeImageView.setImageBitmap(bitmap)
            } catch (e: Exception) {
                qrCodeImageView.setImageResource(R.drawable.placeholder)
            } finally {
                inputStream.close()
            }
        } else {
            qrCodeImageView.setImageResource(R.drawable.placeholder)
        }
    }
}

การดึงและแสดงรายละเอียดสินค้า:

@SuppressLint("SetTextI18n")
private fun fetchProductDetails(productId: Int) {
    productRepository.getProductDetails(productId) { product ->
        if (product != null) {
            try {
                val productDetails = product.detail
                detail.text = productDetails
            } catch (e: Exception) {
                detail.text = "No data found"
            }
        } else {
            detail.text = "Error fetching details"
        }
    }
}

การซ่อนแถบ Action Bar และระบบ Temi:

supportActionBar?.hide()
Utils.hideSystemBars(window)
robot.hideTopBar()

ปุ่มสำหรับปิดหน้าจอ QR Code:

fun onCloseButtonClick(view: View) {
    setResult(RESULT_OK)
    finish()
}

5. VideoActivity.kt

วัตถุประสงค์VideoActivity.kt เป็นหน้าจอแสดงวิดีโอบนหน้าจอของ Temi robot โดยมีการเชื่อมต่อกับบริการ RabbitMQ เพื่อควบคุมหุ่นยนต์ Temi และใช้ VideoView ในการเล่นวิดีโอ. หน้าจอนี้ยังมีการตรวจจับการสัมผัสของผู้ใช้เพื่อนำไปยังหน้าจอหลัก (MainActivity) เมื่อมีการแตะหน้าจอ.

การทำงาน:

  1. เมื่อหน้าจอเริ่มทำงาน (onCreate), แอปจะซ่อนแถบ Action Bar และแสดงหน้าจอเต็มจอ รวมถึงการตั้งค่าการแสดงผลในแนวนอน (SCREEN_ORIENTATION_LANDSCAPE).
  2. แอปจะเชื่อมต่อกับบริการ RabbitMQ ผ่าน bindService() เพื่อเตรียมควบคุมหุ่นยนต์ Temi.
  3. การเล่นวิดีโอจะถูกตั้งค่าใน VideoView ผ่าน URI ของไฟล์วิดีโอในโฟลเดอร์ raw และวิดีโอจะถูกเล่นในโหมดวนลูป (isLooping = true).
  4. หากผู้ใช้สัมผัสหน้าจอ (onTouch), แอปจะเปลี่ยนไปยังหน้าจอหลัก (MainActivity).
  5. เมื่อหุ่นยนต์ Temi พร้อมใช้งาน (onStart), ระบบจะทำการปรับมุมกล้องของหุ่นยนต์โดยการเรียก robot.tiltAngle(0, 1f) เมื่อวิดีโอและหุ่นยนต์พร้อมใช้งานทั้งคู่.

องค์ประกอบสำคัญ:

  • RabbitMQService: บริการที่ใช้เชื่อมต่อกับ RabbitMQ เพื่อควบคุมหุ่นยนต์ Temi โดยใช้การเชื่อมต่อผ่าน ServiceConnection.
  • VideoView: ใช้สำหรับการเล่นวิดีโอ โดยไฟล์วิดีโอถูกดึงจาก res/raw และกำหนดให้เล่นซ้ำในโหมดวนลูป.
  • Temi SDK: ใช้ในการควบคุมหุ่นยนต์ Temi โดยการซ่อนแถบด้านบน (robot.hideTopBar()) และการปรับมุมกล้องของหุ่นยนต์ (robot.tiltAngle(0, 1f)).

หน้าวีดีโอ โชว์หน้าหุ่นยนต์แบบน่ารักเพื่อดึงดูดลูกค้า

ส่วนประกอบสำคัญของโค้ด:

การเชื่อมต่อกับ RabbitMQ Service:

private val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        val binder = service as RabbitMQService.RabbitBinder
        rabbitMQService = binder.getService()
        isBound = true
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        rabbitMQService = null
        isBound = false
    }
}

การตั้งค่าและเล่นวิดีโอใน VideoView:

private fun setupVideoView() {
    temiface = findViewById(R.id.videoView)
    val uri = Uri.parse("android.resource://$packageName/${R.raw.poomjaibot_eye_face}")
    temiface.setVideoURI(uri)

    temiface.setOnPreparedListener { mp ->
        temiface.start()
        mp.isLooping = true

        isVideoPrepared = true
        maybeTiltRobot()  // Try tilting robot if robot is also initialized
    }
}

การตรวจจับการสัมผัสหน้าจอเพื่อนำไปยังหน้าหลัก:

temiface.setOnTouchListener { _, event ->
    if (event.action == MotionEvent.ACTION_DOWN) {
        changeToMainMenu()
    }
    false
}

ฟังก์ชันสำหรับเปลี่ยนไปหน้าจอหลัก (MainActivity):

private fun changeToMainMenu() {
    val intent = Intent(this, MainActivity::class.java)
    startActivity(intent)
    finish()
}

การปรับมุมกล้องของหุ่นยนต์ Temi:

private fun maybeTiltRobot() {
    if (isRobotInitialized && isVideoPrepared) {
        robot.tiltAngle(0, 1f)
    }

ภาพถ่ายและวีดีโอของการทำงานของระบบ

ดูเพิ่มเติมได้ที่: https://drive.google.com/drive/folders/1-1XM4u7lp6DAVjn_iuWdVpLbiYnE0Ndi?usp=drive_link


Feedback หลังจากทดสอบการทำงานจริง

Control Feedback:

  • ผู้ปฏิบัติการหุ่นยนต์มองว่าการควบคุมหุ่นยนต์ด้วยปุ่ม ‘w, a, s, d’ สามารถใช้งานได้ง่าย แต่อยากให้มีการกดปุ่ม interface กับหน้าจอโดยตรงเป็นส่วนเสริมเนื่องจากต้องกดเปิดกล้องอยู่แล้ว

Robot Responsiveness:

  • ผู้ปฏิบัติการหุ่นยนต์มองว่าสามารถใช้งานได้ง่ายเมื่ออยู่ในช่วงทดสอบเนื่องจากเป็นพื้นที่ๆมีสัญญาณอินเตอร์เน็ตที่ดีทำให้ควบคุมหุ่นยนต์
  • ผู้ปฏิบัติการหุ่นยนต์มองว่า เมื่อใช้งานจริงซึ่งเป็นพื้นที่ๆสัญญาณอินเตอร์เน็ตต่ำทำให้เกิดการ delay ระหว่างการควบคุมแต่ยังสามารถปฏิบัติงานได้
  • ทำให้ไม่สามารถหันหรือตอบโต้กับผู้ใช้จอแสดงผลได้ทัน

Control Accuracy Rating:

  • ผู้ปฏิบัติการหุ่นยนต์มองว่าความแม่นยำในการสั่งงานเมื่อทำการกดปุ่มกลางๆ เมื่ออยู่ในช่วงทดสอบเนื่องจากเป็นพื้นที่ๆมีสัญญาณอินเตอร์เน็ตที่ดีทำให้ควบคุมหุ่นยนต์
  • ผู้ปฏิบัติการหุ่นยนต์มองว่าความแม่นยำในการสั่งงานเมื่อทำการกดปุ่มค่อนข้างแย่ เมื่อใช้งานจริงซึ่งเป็นพื้นที่ๆสัญญาณอินเตอร์เน็ตต่ำซึ่งอาจจะส่งผลมาจากส่วนของการ delay

ซึ่งจากสามข้อข้างต้นผู้ปฏิบัติการหุ่นยนต์มองว่าการตอบสนอง (ความรู้สึกต่อการควบคุม) เป็นส่วนที่มีผลกระทบกับการทำงานมากที่สุดและเป็นส่วนสำคัญที่คสรได้รับการปรับปรุง

Interface layout:

  • ผู้ปฏิบัติการหุ่นยนต์มองว่า อยากให้ทำ slot ในการใส่ข้อมูลที่มากขึ้น (art work) เนื่องจากจะได้ใส่รายละเอียดที่ครบถ้วนสมบูรณ์ เช่น ช่องกิจกรรม การลงทะเบียน ตัวอย่าง QR
  • เพิ่มรายละเอียดของฟังชันในการคลิกหน้าจอเพื่อให้ผู้ใช้สามารถเข้าใจวิธีใช้ได้ง่ายขึ้น

Voice Communication Clarity:

  • ผู้ปฏิบัติการหุ่นยนต์ไม่ได้ยินเสียงจากผู้ใช้จอแสดงผล เนื่องจากเมื่อผู้ใช้จอแสดงผลเริ่มใช้งานจอไมโครโฟนจะถูกเลื่อนไปไกลขึ้นทำให้ไม่ได้ยิน

Suggestions for Improvement:

  • เปิดเพลงเพิ่มความน่าสนใจ
  • ระบบ เบรกอัตโนมัติ
  • ทำให้ผู้ปฏิบัติการหุ่นยนต์มองเห็น observation รอบข้างเพื่อป้องการชนความเสียหายต่อหุ่นยนต์
  • ฟังชั่นเสียงจาก text (หุ่นยนต์ journey)

Feedback จากผู้ใช้จอแสดงผล

Interface layout:

ผู้ใช้จอแสดงผลมองว่าการ interact กับจอแสดงผลใช้งานได้ง่าย

Control Feedback:

ผู้ใช้จอแสดงผลส่วนใหญ๋ (85.7%) มองว่าการ interact กับจอแสดงผลตอบสนองได้ดีมาก (ไม่มีความล่าช้า)

ปัจจัยที่ส่งผลต่อการใช้งานหน้าจอแสดงผลมากที่สุดจะเป็นในส่วนของ จำนวนคลิกเนื่องจากอาจจะเพิ่มลูกเล่นเพิ่มเติมเพื่อให้ได้รับรายละเอียดมากขึ้น

Voice Communication Clarity:

ผู้ใช้จอแสดงผลส่วนหนึ่ง (42.9%) มองว่าการในส่วนของเสียงมีความชัดเจนดี และอื่นๆยังคงมองว่าต้องปรับปรุงความชัดเจนของเสียง

Purchase Process Satisfaction:

ผู้ใช้จอแสดงผลส่วนหนึ่ง (42.9%) มองว่าความรวดเร็วในการได้รับข่าวสารค่อนข้างน่าพึงพอใจและบางส่วนยังคงรู้สึกกลางๆ

Suggestions for Improvement:

  • ข้อจำกัดด้านหน้าจอในการแสดงผลข้อมูล ในกรณีข้อมูลยาวอาจจะเพิ่มการเลื่อนข้อมูล
  • เพิ่มความน่ารักจาก interaction ทำให้เป็นจุดสนใจ
  • เพิ่มเติมของตกแต่งที่ทำให้บุคคลทั่วไปได้รับข้อมูลแนะนำเกี่ยวกับหุ่นยนต์ว่าเป็นหุ่นยนต์ประชาสัมพันธ์
  • โชวหน้ากิจกรรมแทนหน้าตาปิ้งเพื่อให้คนรอบๆรู้จุดประสงค์และเข้าหาง่ายขึ้น
  • QR code สำหรับสแกนอ่านข้อมูลเพิ่มเติม
  • เพิ่มเติมการตกแต่ง interface เพื่อดึงดูดความน่าเล่นของ Temi
    • ปรับรูปภาพให้ balance อยากให้ชิดฝั่งไปเลย
  • เพิ่มรายละเอียดวิธีการใช้งาน function
    • สามารถ slide ซ้ายเพื่อดูงานอื่นๆ
  • ปรับปรุงความสูงของหุ่ยนต์ให้ง่ายต่อการ interface แทนที่การก้มเพื่ออ่านข้อมูล

เอกสารอ้างอิง

https://github.com/robotemi/sdk

Source Code

https://github.com/peeradonmoke2002/TemiApp.git

https://github.com/peeradonmoke2002/Temi-webapp.git