บทความ › IoT และ Embedded

ต่อเครื่องพิมพ์ 3D ด้วย MQTT: snapshot + delta ต่อใหม่เอง และเฝ้าว่ายังทำงาน

เรียนรู้ MQTT จากงานจริง ดึงสถานะเครื่องพิมพ์ 3D หลายยี่ห้อมาแสดงบนไลฟ์ ขอ snapshot แล้วรวม delta ต่อใหม่เมื่อหลุด แยก process และเฝ้า token ที่หมดอายุเงียบ ๆ

กลาง อ่าน 7 นาที 16 ตุลาคม 2569

อ่านจบแล้วจะรู้
  • MQTT คือการส่งข้อความผ่านตัวกลางตาม topic อุปกรณ์ส่งเมื่อมีอะไรเปลี่ยน ไม่ต้องให้เราถามซ้ำ ๆ
  • ถ้าอุปกรณ์ส่งแค่ส่วนที่เปลี่ยน (delta) ต้องขอภาพรวม (snapshot) ตอนเริ่ม และขอซ้ำเป็นระยะกันข้อมูลตกหล่น
  • อุปกรณ์ไม่บอกเราเมื่อมันพัง ต้องมีตัวเฝ้าที่ตรวจเองเป็นระยะ และแยก process ที่คุยกับอุปกรณ์ออกจากเซิร์ฟเวอร์หลัก

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

ตัวอย่างใช้ Python 3.11 กับ library paho-mqtt บน Windows และเครือข่ายภายในบ้าน

MQTT ในห้านาที

MQTT คือการส่งข้อความผ่าน ตัวกลาง (broker) ผู้ส่งไม่รู้จักผู้รับ ทั้งสองฝ่ายรู้จักแค่ topic ซึ่งเป็นชื่อช่อง เช่น device/01S00A123/report

  • publish ส่งข้อความเข้า topic
  • subscribe บอก broker ว่าอยากได้ข้อความจาก topic ไหน broker จะส่งมาให้เองทุกครั้งที่มี

ต่างจากการเรียก API ตรงที่เราไม่ต้องถามซ้ำ ๆ ว่ามีอะไรเปลี่ยนไหม อุปกรณ์ส่งมาเองเมื่อมีอะไรเปลี่ยน เหมาะกับอุปกรณ์ที่เน็ตไม่เสถียรและแบตเตอรี่จำกัด

คำความหมาย
QoS 0ส่งครั้งเดียว อาจหาย ใช้กับค่าที่ส่งมาบ่อยอยู่แล้ว
QoS 1รับประกันว่าถึง แต่อาจถึงซ้ำ
QoS 2ถึงครั้งเดียวพอดี ช้าที่สุด
retainedbroker จำข้อความล่าสุดของ topic ไว้ ใครมา subscribe ทีหลังได้ทันที
last willข้อความที่ broker ประกาศแทนเรา ถ้าเราหลุดแบบไม่บอกลา ใช้แจ้งว่า offline
keepaliveระยะที่ต้องส่งสัญญาณว่ายังอยู่ ถ้าเงียบนานเกิน broker ถือว่าหลุด

รูปแบบที่ 1: ขอ snapshot แล้วรวม delta

เครื่องพิมพ์ Bambu Lab เป็น broker ในตัว ต่อผ่าน TLS ที่พอร์ต 8883 บนเครือข่ายภายใน เมื่อต่อได้ เรา subscribe topic สถานะของเครื่อง

ปัญหาคือเครื่องไม่ได้ส่งสถานะทั้งหมดทุกครั้ง ส่งแค่ ส่วนที่เปลี่ยน (delta) เช่น {"print": {"mc_percent": 42}} ถ้าเราต่อเข้าไปตอนกลางงานพิมพ์ จะไม่รู้ชื่องาน อุณหภูมิ หรือเวลาที่เหลือ จนกว่าค่าพวกนั้นจะเปลี่ยน

วิธีแก้คือ ขอภาพรวมทันทีที่ต่อได้ แล้วรวม delta ที่ตามมาลงไปเรื่อย ๆ

def on_connect(client, userdata, flags, rc, properties=None):
    client.subscribe(f"device/{serial}/report")
    client.publish(f"device/{serial}/request",
                   json.dumps({"pushing": {"sequence_id": "0", "command": "pushall"}}))

def on_message(client, userdata, msg):
    payload = json.loads(msg.payload)
    state.update(payload.get("print", {}))   # รวม delta ลงในภาพรวมที่มีอยู่

และ ขอภาพรวมซ้ำทุก 15 วินาที แม้ไม่มีอะไรผิดปกติ เพราะถ้า delta ตัวไหนหายระหว่างทาง ภาพรวมที่เราถืออยู่จะผิดไปตลอด การขอซ้ำเป็นระยะทำให้ความผิดพลาดอยู่ได้ไม่เกิน 15 วินาที

หลักการ: ระบบที่ส่งแค่ส่วนที่เปลี่ยน ต้องมีทางขอภาพรวมทั้งหมดเสมอ ทั้งตอนเริ่มและเป็นระยะ

เครื่องพิมพ์ (broker)process ข้างเคียงsubscribe .../reportpublish pushallภาพรวมทั้งหมดdelta: mc_percent 42delta: …ทุก 15 วิ: pushall อีกครั้งstatus.json (มี ts) · เขียนไม่ถี่กว่า 2 วิเซิร์ฟเวอร์ไลฟ์อ่านไฟล์
ขอภาพรวมตอนเริ่มและทุก 15 วินาที รวม delta ระหว่างนั้น แล้วเขียนลงไฟล์ให้เซิร์ฟเวอร์อ่าน

รูปแบบที่ 2: หลุดแล้วต่อใหม่เอง

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

client.reconnect_delay_set(min_delay=1, max_delay=30)
client.connect(host, 8883, keepalive=30)
client.loop_start()

reconnect_delay_set ให้ลองต่อใหม่ห่างขึ้นเรื่อย ๆ เริ่มที่ 1 วินาที ไม่เกิน 30 วินาที ไม่ถี่จนท่วมเครื่องพิมพ์ที่เพิ่งเปิด และไม่ห่างจนข้อมูลหายนาน

อีกจุดคือ ตอนหลุด ให้ประกาศว่า offline ทันที อย่าปล่อยให้หน้าจอแสดงค่าสุดท้ายค้างไว้ราวกับยังทำงาน

def on_disconnect(client, userdata, *args):
    state_online = False
    write_status()   # เขียนสถานะ offline ทันที ไม่รอรอบถัดไป

รูปแบบที่ 3: แยก process ที่คุยกับอุปกรณ์

ตอนแรกเราให้เซิร์ฟเวอร์หลักต่อ MQTT เอง แต่การเชื่อมต่อหมดเวลาทุกครั้ง ทั้งที่รหัสและเครือข่ายถูก สาเหตุคือบน Windows เซิร์ฟเวอร์ต้องใช้ event loop แบบที่รันโปรแกรมย่อยได้ (สำหรับ ffmpeg) แต่ library MQTT ต้องการ event loop อีกแบบ สองแบบอยู่ในโปรแกรมเดียวกันไม่ได้

ทางแก้คือแยก process เล็ก ๆ ข้างเคียง (side-car) ที่ทำหน้าที่เดียว คุยกับเครื่องพิมพ์ แล้วเขียนสถานะลงไฟล์ JSON ทุก 2 วินาทีเป็นอย่างถี่ เซิร์ฟเวอร์หลักแค่อ่านไฟล์

{"online": true, "progress": 42, "remaining_min": 87, "nozzle": 219.5, "ts": 1760598000}

ได้มากกว่าที่ตั้งใจ

  • เครื่องพิมพ์มีปัญหา เซิร์ฟเวอร์ไม่ล่มตาม process ข้างเคียงพังก็รีสตาร์ตแยกได้
  • มี ts บอกเวลา ถ้าไฟล์ไม่ถูกอัปเดตนานเกินไป ฝั่งที่อ่านรู้ว่าข้อมูลเก่า แสดงว่าไม่ทราบสถานะ แทนที่จะแสดงค่าผิด
  • ทดสอบง่าย ปลอมไฟล์สถานะได้เลย ไม่ต้องมีเครื่องพิมพ์จริง

รูปแบบที่ 4: เฝ้าสิ่งที่หมดอายุเงียบ ๆ

เครื่องพิมพ์อีกยี่ห้อส่งลิงก์วิดีโอผ่าน MQTT ลิงก์มี token ที่ หมดอายุเอง เมื่อหมดแล้ว ลิงก์ตอบ 404 ตัวดึงวิดีโอก็พยายามต่อลิงก์ตายซ้ำไปเรื่อย ๆ ภาพบนไลฟ์ค้างที่ 0 เฟรม โดยไม่มีอะไรแจ้งเตือนเลย

เราจึงมีตัวเฝ้าที่รันทุก 30 วินาที

  1. ลองเปิดลิงก์วิดีโอปัจจุบัน
  2. ถ้าตอบ 404 หรือไม่มีข้อมูล สั่งเครื่องพิมพ์เริ่มจับภาพใหม่ผ่าน MQTT แล้วขอสถานะใหม่เพื่อเอา token ใหม่
  3. ถ้า token เปลี่ยน ตั้งค่าตัวดึงวิดีโอใหม่ แล้วรอจนเห็นข้อมูลเข้าจริง

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

ความปลอดภัย

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

ข้อผิดพลาดที่เจอบ่อย

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

คำถามที่พบบ่อย

ต้องตั้ง broker เองไหม

แล้วแต่อุปกรณ์ เครื่องพิมพ์บางยี่ห้อเป็น broker ในตัวบนเครือข่ายภายใน เราต่อเข้าไปได้เลย ถ้าเป็นเซนเซอร์ที่ทำเองด้วย ESP32 ส่วนใหญ่ต้องมี broker กลาง เช่น Mosquitto รันบนเครื่องในบ้านหรือเซิร์ฟเวอร์

QoS ควรใช้ระดับไหน

ข้อมูลสถานะที่ส่งมาบ่อย ๆ ใช้ QoS 0 ก็พอ เพราะเดี๋ยวก็มีค่าใหม่ตามมา คำสั่งที่ต้องไปถึงแน่ เช่น สั่งหยุดพิมพ์ ใช้ QoS 1 และทำให้คำสั่งนั้นส่งซ้ำได้โดยไม่เกิดผลซ้ำ

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#IoTกลาง

เขียน ESP32 ด้วย Rust: esp-hal 1.0 อะไรเสถียรแล้ว และไฟกะพริบตัวแรก

esp-hal 1.0 คือ Rust SDK สำหรับ ESP32 ที่ Espressif หนุนเอง GPIO UART SPI I2C เสถียรแล้ว Wi-Fi ยังไม่ ตั้งเครื่องมือ สร้างโปรเจกต์ด้วย esp-generate และเขียนไฟกะพริบ

อ่าน 10 นาที
#IoTกลาง

อัปเดต ESP32 ผ่าน Wi-Fi ไม่ให้เครื่องพัง: OTA สองช่อง rollback และ signed image

OTA บน ESP-IDF ที่ปลอดภัย: ota_0/ota_1 + otadata เปิด app rollback ให้เฟิร์มแวร์ใหม่ต้องผ่าน self-test ดาวน์โหลดผ่าน HTTPS ตรวจลายเซ็น และคำนวณพาร์ทิชันบน flash 4 MB

อ่าน 14 นาที
#IoTกลาง

กล้องวงจรปิดบน Matter 1.5: ดูภาพในบ้านผ่าน WebRTC และตั้งให้เป็นส่วนตัว

Matter 1.5 และ 1.5.1 เพิ่มกล้องเข้ามาในมาตรฐาน สตรีมด้วย WebRTC ในวงแลน ออกนอกบ้านผ่าน STUN/TURN มี privacy zone บทความนี้อธิบายกลไกและวิธีตั้งให้ภาพอยู่ในบ้าน

อ่าน 7 นาที