ภาพพื้นหลังปกบทเรียน

บทเรียน 4.6 — ลงมือทำ: telemetry สองทาง

MQTT (1883) · Telemetry ขาออก และ Command ขากลับ บน broker ของเราเอง

โมดูล 4 — เชื่อมต่อแพลตฟอร์ม IoT

ต่อจากบทเรียน 4.5 — MQTT กับแพลตฟอร์มที่ติดตั้งเอง: telemetry และ command

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

MVP checkpoint — ผ่านชุดบทเรียนนี้เมื่อ

สองทาง — publish JSON เซนเซอร์จริงทุก 5 วินาที + สั่ง toggle LED จาก MQTT Explorer ผ่าน topic ของทีม

แปลเป็นสิ่งที่ตรวจได้จริง:

  • [ ] MQTT Explorer เห็นข้อความเข้ามาที่ topic ของทีม ห่างกัน 5 วินาที ต่อเนื่องอย่างน้อย 1 นาที
  • [ ] payload เป็น JSON ที่ถูกต้อง มีค่าจากเซนเซอร์จริงอย่างน้อย 3 ฟิลด์ และค่าจะเปลี่ยนเมื่อขยับบอร์ด
  • [ ] พิมพ์ {"cmd":"toggle"} จาก MQTT Explorer แล้ว LED บนบอร์ดสลับสถานะ ได้ทั้งติดและดับ
  • [ ] ข้อมูลขึ้นกราฟใน Device Details → Telemetry ของ TESAIoT CE ที่ติดตั้งเอง
  • [ ] อธิบายได้ว่า client_id, username, device_id และช่องที่สองของ topic ต้องสัมพันธ์กันอย่างไร
  • [ ] บันทึกภาพหน้าจอทั้งสองฝั่งลงบันทึกการเรียน

ข้อที่สามคือหัวใจ — ถ้าขาดข้อนี้ เราสร้างได้แค่ เครื่องส่งข้อมูล ยังไม่ใช่ อุปกรณ์ที่สั่งได้

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

กับดักที่เจอบ่อย

อาการ สาเหตุที่แท้จริง วิธีแก้
connect() คืน False ทุกครั้ง ทั้งที่รหัสถูก CE เผยแพร่พอร์ตเป็น 127.0.0.1:11883 บอร์ดใน LAN เข้าไม่ถึง แก้ compose เป็น 0.0.0.0:1883:1883 แล้ว up -d emqx
ต่อไม่ติด และไม่มีข้อความบอกสาเหตุ device_id เป็น UUID 36 ตัว ถูกตัดเหลือ 31 ตั้ง device_id เองให้สั้น แล้วขึ้นทะเบียนใหม่
TypeError: unexpected keyword argument พิมพ์ keep_alive ตามเอกสารใน IDE ซึ่งเขียนผิด ใช้ keepalive
publish ไม่ error แต่แพลตฟอร์มไม่มีข้อมูล ช่องที่สองของ topic ไม่ตรงกับ device_id → ACL ปฏิเสธ ใช้ device/<device_id>/telemetry ตรงตัวอักษร
ข้อมูลเข้า แต่ไม่มีเส้นกราฟ ค่าเป็นสตริง — กราฟรับเฉพาะตัวเลข เปลี่ยนเป็นตัวเลข เช่น {"ok": 1}
กดคำสั่งรัว ๆ แล้วได้ผลแค่ครั้งสุดท้าย ช่องรับมีช่องเดียว ข้อความใหม่ทับของเก่า poll ทุก 100 ms และอย่าใช้ time.sleep(5) คร่อมทั้งลูป
คำสั่งยาว ๆ ทำให้ json.loads พัง payload ขาเข้าเกิน 255 ไบต์ ถูกตัดกลางคัน คำสั่งต้องสั้น และมี try/except
สองทีมหลุดสลับกันเป็นจังหวะ ใช้ client_id ซ้ำกัน broker เตะตัวเก่าออก หนึ่งทีมหนึ่ง client_id เสมอ
ต่อได้ตอนแรก แล้วหลุดทุก ๆ ราวหนึ่งนาที เงียบนานเกิน keepalive ส่งถี่กว่าค่า keepalive หรือขยับค่านั้นขึ้น
TypeError ตอนใส่ retain=True retain มีในเอกสารแต่ไม่มีในตัวจริง รับได้แค่ 3 อาร์กิวเมนต์ ตัด retain ออก ถ้าต้องการค่าคงค้างต้องตั้งฝั่ง broker
ชื่อ topic ขาเข้ายาว ๆ อ่านแล้วแยกไม่ออกว่ามาจากใคร topic ขาเข้าถูกตัดที่ 127 ไบต์ เงียบ ๆ ตั้งชื่อ topic ให้สั้น อย่าซ้อนลึกเกินสี่ชั้น
OSError ตอนเรียก publish() ทั้งที่โค้ดเดิมเคยผ่าน ลิงก์หลุดไปก่อนแล้ว publish() ตอนไม่ได้ต่อโยน error ไม่ได้คืน False เช็ก is_connected() ก่อน และครอบ publish() ด้วย try/except OSError

สิบเอ็ดจากสิบสองแถวนี้ ไม่ส่ง error ที่ตรงกับสาเหตุ — จึงต้องอ่านตารางนี้ก่อนเจอปัญหา ไม่ใช่หลังเจอ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ลงมือทำ — เติมช่องว่างในไฟล์ฝึก

s10_mqtt_telemetry.py pass ท่า 1 — mqtt.connect(...) (1 จุด) pass ท่า 2 — ประกอบ dict + publish (2 จุด) pass ท่า 3 — subscribe + get_message + LED (3 จุด) รวม 6 จุด — เติมทีละจุด แล้วรันทุกครั้ง

เปิด s10_mqtt_telemetry.py มีช่องว่างให้เติม 6 จุด

# เติม: ok = mqtt.connect(BROKER, port=1883, client_id=DEVICE_ID, username=DEVICE_ID, password=MQTT_PASS, keepalive=60)
pass
        # เติม: data = {"ax": round(ax, 2), "ay": round(ay, 2), "az": round(az, 2), "pot": round(sensors.pot.percent(), 1)}
        pass
    # เติม: mqtt.publish(TOPIC_PUB, json.dumps(data))
    pass
# เติม: mqtt.subscribe(TOPIC_CMD)
pass
    # เติม: msg = mqtt.get_message()
    pass
            # เติม: lamp.value(1 if led_on else 0)
            pass

หนึ่ง แก้ค่าเจ็ดบรรทัดบนหัวไฟล์ สอง เติมท่าที่ 1 แล้วรันจนเห็นว่าต่อแล้ว สาม เติมท่า 2-3 ทีละจุด

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

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ปิดวงจร — เอาเลขแต่งออก ใส่เซนเซอร์จริงเข้าไป

เจ็ดไฟล์ที่ผ่านมาส่งเลขที่เราแต่งขึ้นเอง โดยตั้งใจ — กลไก MQTT มีเรื่องให้ผิดพลาดมากพออยู่แล้ว · ตอนนี้กลไกแน่นแล้ว 08_real_sensor_leaves_the_board.py เอาเลขแต่งออก แล้วต่อ ชิปบนบอร์ด → WiFi → broker ให้ครบวง

ค่าที่ไฟล์นี้ส่งคือ อุณหภูมิ อ่านผ่านฟังก์ชัน read_temp() ในไฟล์ — sensors.snapshot() ไม่มีช่องอุณหภูมิบนบอร์ดไหนเลย ไฟล์จึงถามก่อนว่าบอร์ดมี sensors.sht40 ไหม: บน Dev Kit ได้อุณหภูมิห้องจริงจาก SHT40 ส่วน บน Eva ไม่มีเซนเซอร์อุณหภูมิ ลูกบิดจึงเล่นบทแทน (0–100 % = 15–45 °C) และ console บอกไว้ตั้งแต่รอบแรกว่าค่ามาจากไหน — หมุนลูกบิดแล้วเห็นเส้นวิ่งทั่วกราฟช่วง 20–35 °C ได้บนโต๊ะ ไฟล์นี้ยังไม่มีเกณฑ์ตัดสิน (เงื่อนไข 0.3 องศาคือการบ้านท้ายไฟล์)

ตัวเลขที่ต้องแยกให้ออก ในไฟล์นี้ ทำไม
รอบวัด ทุก 200 ms การวัดไม่กวนใคร วัดถี่ได้ตามใจ
รอบส่ง ทุก 2000 ms การส่งกวน broker และกวนคนอื่นที่ใช้ broker เดียวกัน

เลขสองตัวนี้ไม่เท่ากัน และไม่ควรเท่ากัน — ถ้าส่งทุก 200 ms คือ 5 ข้อความต่อวินาทีต่อบอร์ด บอร์ดสิบห้าตัวก็ 75 ข้อความต่อวินาทีเข้า broker ตัวเดียว นั่นคือวิธีทำให้ broker ล่มโดยไม่มีใครเขียนโค้ดผิดสักบรรทัด

บนจอจะเห็นสองเส้น — ฟ้าคือค่าที่วัดได้ทุกรอบ เขียวคือค่าที่ส่งออกไปจริง เส้นเขียวเป็นขั้นบันได นั่นคือภาพของประโยคที่จริงเสมอในงาน IoT: จอเห็นบ่อยกว่าที่คลาวด์เห็น

ตาคุณ — เพิ่มเงื่อนไข "ส่งเฉพาะเมื่อค่าเปลี่ยนเกิน 0.3 องศา" แล้วดูว่าจำนวนครั้งที่ส่งลดลงเท่าไร โดยที่คนดูปลายทางยังเห็นภาพเดิมทุกประการ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เชื่อมโยงรากฐาน + สรุปบทเรียน

ฝั่งสมองกลฝังตัว บัฟเฟอร์ขนาดจำกัด · การ poll งบหน่วยความจำเป็นข้อจำกัดจริง เครือข่ายกับจอคนละคอร์ ฝั่ง Python และเครือข่าย dict → JSON · bytes vs str pub/sub · topic · QoS try/except กับข้อมูลจากภายนอก ฝั่งออกแบบระบบ สัญญาระหว่างระบบ (topic/schema) แลกความถี่กับต้นทุน ความล้มเหลวที่เงียบ ต้องรู้ล่วงหน้า

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

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

งานทำเอง 30%: เลือกทำ 1 ข้อจากสี่ข้อในสไลด์ต่อยอด จดลงบันทึกการเรียน

ชุดบทเรียนถัดไป: เปลี่ยนจากพอร์ต 1883 ไปเป็น 8884 พร้อม TLS แล้วดักจับสัญญาณเทียบกันให้เห็นด้วยตาว่าสิ่งที่คนดักได้ต่างกันอย่างไร

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

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย s10_mqtt_telemetry.py — ส่วนที่หนึ่ง

WIFI_SSID = "my-hotspot"                # WiFi บ้านหรือ Hotspot มือถือ วงเดียวกับคอมที่รัน CE
WIFI_PASSWORD = "<รหัสผ่าน WiFi ของคุณ>"
BROKER = "192.168.1.50"                # IP ของเครื่องที่รัน TESAIoT CE ในแลน (ไม่ใช่ localhost)
DEVICE_ID = "team03"                   # ต้องตรงกับ device_id ที่ขึ้นทะเบียน · <= 31 ตัวอักษร
MQTT_PASS = "<รหัสผ่าน MQTT ของคุณ>"   # ได้จากตอนเพิ่มอุปกรณ์ใน CE ที่คุณติดตั้ง (บทเรียน 4.5)
TOPIC_PUB = "device/team03/telemetry"
TOPIC_CMD = "device/team03/commands"
...
try:
    sensors.bmi270.motion()            # อุ่นเครื่องหนึ่งครั้ง ให้การรอไปเกิดก่อนต่อเน็ต
except OSError:
    print("อ่านเซนเซอร์รอบแรกยังไม่ได้ - ลองใหม่ตอนส่ง")
...
wifi.connect(WIFI_SSID, WIFI_PASSWORD)
lcd.print("WiFi:", wifi.ip())
ค่าที่ต้องแก้ทั้งหมด อยู่บนหัวไฟล์ 7 บรรทัด ไม่ต้องแก้อะไรข้างล่างอีก คนมาแก้ทีหลังหาเจอทันที DEVICE_ID โผล่ 3 ที่ client_id · username · topic จึงประกาศเป็นตัวแปรตัวเดียว ทำให้ไม่มีทางไม่ตรงกัน ไม่ต้องเรียก sensors.init() Eva: เรียกแล้วได้ OSError Dev Kit: ปลุกไว้ตั้งแต่บูตแล้ว Eva: อ่านครั้งแรกอาจรอถึง 16 วิ

บน Eva Kit คอร์จอ (CM55) เป็นเจ้าของบัสเซนเซอร์ sensors.init() และ sensors.scan() จึงถูกปฏิเสธด้วย OSError ส่วน sensors.bmi270.* capsense.* pot.* เรียกได้ทันทีผ่าน snapshot ที่คอร์จอเก็บไว้ให้ · บน Dev Kit init() ทำงานได้จริง แต่ก็ไม่ต้องเรียก เพราะเฟิร์มแวร์ปลุกเซนเซอร์ไว้ตั้งแต่บูต — โค้ดเดียวกันจึงรันได้ทั้งสองบอร์ด · sensors.snapshot() คืน dict รูปเดียวกันทั้งสองบอร์ด (คีย์ bmi270 capsense pot) และไม่มีคีย์อุณหภูมิ

เขียนค่าที่ต้องตรงกันไว้ที่เดียว แล้วบั๊กประเภท "ลืมแก้ที่หนึ่ง" จะหายไปทั้งตระกูล

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — แผงเฝ้าลิงก์ สร้าง ก่อน ต่อเน็ต

ui.screen()
time.sleep_ms(200)
ui.Label("MQTT Telemetry - ชุด 10", x=24, y=36, color=COL_TEXT, value=20)
led_wifi = ui.Led(x=328, y=16, w=48, h=48, color=COL_OK, value=0)
...
led_mqtt = ui.Led(x=440, y=16, w=48, h=48, color=COL_OK, value=0)
...
led_stale = ui.Led(x=552, y=16, w=48, h=48, color=COL_WARN, value=0)
...
led_remote = ui.Led(x=664, y=16, w=48, h=48, color=COL_RUN, value=0)
...
ui.Panel(x=24, y=112, w=232, h=280, color=COL_CARD, min=COL_DIM, max=12, value=1)
...
lbl_pot = ui.Label("- %", x=40, y=160, color=COL_TEXT, value=24)
bar_pot = ui.Bar(x=40, y=204, w=200, h=12, color=0x4A9EFF, min=0, max=100, value=0)
sc_pot = ui.Scale(x=40, y=224, w=200, h=44, color=COL_TEXT, min=0, max=100)
sc_pot.ticks(11, 5)
...
seg_sent = ui.Seg7("0", x=40, y=324, w=200, h=48, color=COL_TEXT)
...
lst_sent = ui.List(x=288, y=160, w=208, h=168)
...
btn_go = ui.Button("เริ่มส่ง", x=544, y=240, w=88, h=88, color=0x30A46C, value=20)
btn_hold = ui.Button("หยุดส่ง", x=664, y=240, w=88, h=88, color=0x3A4150, value=20)
ui.poll()

wifi.connect(WIFI_SSID, WIFI_PASSWORD)
lcd.print("WiFi:", wifi.ip())
led_wifi.value(1 if wifi.is_connected() else 0)

จอถูกสร้าง ก่อน wifi.connect() โดยตั้งใจ — การต่อเน็ตคือช่วงที่น่าดูที่สุดของโปรแกรมนี้ ถ้าสร้างจอทีหลัง ช่วงนั้นผ่านไปโดยไม่มีใครเห็น · ไฟสี่ดวงไล่ตาม เส้นทางจริงของข้อมูล: WiFi → MQTT → "ค่าค้าง" → หลอดจริงที่คนอีกห้องสั่งได้ · ui.Scale ใต้ ui.Bar ทำให้ค่า pot มีพิสัยกำกับ — ตัวเลข 14.6 ที่มีไม้บรรทัด 0–100 อยู่ใต้มันบอกทันทีว่าสูงไหม · การ์ดสร้างก่อนของที่วางบนมัน (LVGL วาดตามลำดับสร้าง) · ปุ่มสองปุ่มสูง 88 = เป้าสัมผัสตามเกณฑ์ และจบที่ y=328 เพราะมุมขวาล่างเป็นของปุ่ม Console

ui.List ไม่ใช่ ui.Label เรียงกัน เพราะรายการนี้ถูกล้างแล้วเขียนใหม่ทุกห้าวินาที ถ้าทำด้วย Label ต้องนับพิกเซลใหม่ทุกครั้งที่ข้อความยาวไม่เท่าเดิม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สอง: ลูปหลัก และทำไมเรียงสามท่าแบบนี้

mqtt.subscribe(TOPIC_CMD)                  # ต่อจากท่าที่ 1 ที่ connect แล้ว
...
while True:
    if sending and time.ticks_diff(time.ticks_ms(), t_last) >= SEND_MS:
        d = publish_telemetry()            # ท่าที่ 2 ทั้งท่าอยู่ในฟังก์ชันนี้
        if d is not None:
            sent += 1
            t_good = time.ticks_ms()
            ...
            lbl_pot.text(str(pot) + " %")  # จอขยับทุก 5 วินาที ไม่ใช่ทุกรอบลูป
            bar_pot.value(int(pot))
            seg_sent.text(str(sent))
            ...
        t_last = time.ticks_ms()
    ...
ท่า 1 — ต่อได้จริง ท่า 2 — ข้อมูลออกได้ ท่า 3 — คำสั่งเข้าได้ ครบสองทาง แต่ละท่ายืนยันท่าก่อนหน้า

ท่า 2 ส่งออกก่อนรับเข้า เพราะขาส่งตรวจง่ายกว่า — ถ้าถึงท่า 3 แล้วไม่ทำงาน รู้แน่ว่าปัญหาอยู่ที่ topic ของคำสั่ง

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สอง (ต่อ): ครึ่งหลังของลูป — สามนาฬิกาในลูปเดียว

    ...
    stale = time.ticks_diff(time.ticks_ms(), t_good) >= STALE_MS
    if stale != stale_shown:               # เขียนเฉพาะตอนเปลี่ยน
        stale_shown = stale
        led_stale.value(1 if stale else 0)

    msg = mqtt.get_message()               # ถามทุกรอบลูป
    if msg is not None:
        ...                                # แปลง JSON แล้วสั่งไฟ - เหมือนท่าที่ 3
    if time.ticks_diff(time.ticks_ms(), t_ui) >= 200:
        t_ui = time.ticks_ms()
        for ev in ui.poll():
            ...                            # ปุ่มเริ่มส่ง / หยุดส่ง
    time.sleep_ms(100)

สังเกตว่าลูปนี้มีนาฬิกา สามเรือน ไม่ใช่เรือนเดียว: เรือนของการส่ง (5 วินาที) เรือนของนิ้ว (200 ms) และเรือนของ get_message() ซึ่งถามทุกรอบ ถ้ายุบทั้งสามให้เดินจังหวะเดียวกัน จะได้โปรแกรมที่ไม่ตอบคำสั่งหรือไม่ก็ยิงจอทิ้งเปล่า

if stale != stale_shown: ไม่ได้เขียนไว้ให้สวย — คิวคำสั่งของจอมีก้นถัง พอมันเต็ม เฟิร์มแวร์ทิ้งคำสั่งเปลี่ยนข้อความก่อนเป็นอย่างแรก (ipc_ui.c ระบุ SET_TEXT ว่าเป็นคำสั่งที่ทิ้งได้) โปรแกรมที่ยิงคำสั่งจอสิบครั้งต่อวินาที จึงเห็นตัวเลขค้างเป็นบางครั้งโดยไม่มี error สักบรรทัด แก้ที่ยิงให้น้อยลง ไม่ใช่ยิงซ้ำให้มากขึ้น

try/except มีไว้เพราะคำสั่งมาจากคนพิมพ์ — โปรแกรมต้องไม่ตายเพราะคนอื่นพิมพ์ผิด

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เชื่อมจุดให้เห็นภาพ — วันนี้อยู่ตรงไหนของเส้นทาง

บทเรียน 1.1–2.9 สั่งฮาร์ดแวร์ ในบอร์ดของเราเอง บทเรียน 3.1–4.3 อ่านเซนเซอร์ วาดกราฟ แล้วต่อเน็ตได้ วันนี้ · บทเรียน 4.4–4.6 ข้อมูลออกไปนอกบอร์ด และคำสั่งเดินกลับเข้ามา บทเรียน 4.7–5.3 ทำให้ปลอดภัย ประกอบเป็นสินค้า วันนี้คือจุดที่ "โครงงานบนโต๊ะ" กลายเป็น "ระบบที่มีหลายเครื่อง"

คำถามคิดต่อ: ถ้าเน็ตของเราหลุดไปสองนาที ข้อมูลช่วงนั้นควรหายไปเลย หรือบอร์ดควรเก็บไว้ส่งทีหลัง · ใครควรเป็นคนตัดสินใจว่าค่าไหนผิดปกติ ระหว่างบอร์ดกับแพลตฟอร์ม · ถ้ามีอุปกรณ์ 500 ตัวส่งทุก 5 วินาที broker ตัวเดียวรับไหวไหม และเราจะรู้ได้อย่างไรก่อนจะสาย

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ใช้จริงที่ไหน — สี่มุมที่ MQTT ทำงานอยู่ตอนนี้

โรงงาน · สายการผลิตหลายร้อยจุด เซนเซอร์แต่ละตัว publish ขึ้น topic ของสายผลิตตัวเอง ระบบซ่อมบำรุง subscribe ด้วย wildcard ครั้งเดียว เพิ่มเครื่องจักรใหม่ ไม่ต้องแก้โปรแกรมฝั่งใดเลย อาคาร · ระบบควบคุมส่วนกลาง แอร์ ไฟ ม่าน รับคำสั่งผ่าน topic ของห้องตัวเอง สั่งทั้งชั้นพร้อมกันได้ด้วยการ publish ครั้งเดียว นี่คือขากลับแบบเดียวกับ toggle LED ของเราวันนี้ เกษตร · แปลงที่สัญญาณไม่ดี ส่วนหัวสองไบต์ทำให้ส่งผ่านลิงก์แคบ ๆ ได้ ส่งวันละไม่กี่ครั้ง แบตอยู่ได้เป็นฤดูกาล เลือกความถี่จากธรรมชาติของค่าที่วัด ขนส่ง · รถที่วิ่งอยู่ตลอดเวลา สัญญาณหลุดเป็นเรื่องปกติ ไม่ใช่ข้อยกเว้น QoS 1 สำหรับสิ่งที่หายไม่ได้ · QoS 0 สำหรับพิกัด การเลือก QoS คือการเลือกว่าอะไร "หายได้"

ทั้งสี่มุมนี้ใช้คำสั่งชุดเดียวกับที่เราเพิ่งเขียนวันนี้ ต่างกันแค่จำนวนอุปกรณ์และความสำคัญของข้อมูล

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ดูเพิ่มเติมนอกเวลา — วิดีโอที่ตรวจแล้วว่าเปิดได้

เรียนรู้ MQTT และควบคุมอุปกรณ์ IoTs จากทุกมุมโลกด้วย MQTT เข้าใจง่าย — IT around U · 21 นาที 26 วินาที · ไทย — ตัวเลือกภาษาไทยที่ครบที่สุด: ตั้ง broker เอง สาธิต pub/sub และคุมอุปกรณ์จริง (ข้ามส่วนติดตั้งได้ที่นาทีที่ 10)

MQTT Essentials Part 1 — What is MQTT — HiveMQ · 6 นาที 26 วินาที · อังกฤษ — อธิบายว่าทำไม IoT ไม่เลือก HTTP ตั้งแต่ต้น เป็นตอนแรกของซีรีส์ที่เราหยิบตอน 6 และ 7 มาใช้แล้ว

อ่านต่อสำหรับคนอยากรู้ลึก

คลิปเหล่านี้ไม่อยู่ในเกณฑ์ผ่าน แต่คนที่ดูจะเข้าใจบทเรียน 4.7–4.9 เรื่อง TLS ได้เร็วขึ้น

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ต่อยอด — คิดต่อเอง (เลือกทำ 1 ข้อ)

1 · ฟังทุกอุปกรณ์ device/+/telemetry แล้วเทียบ schema กัน 2 · คำสั่งชุดใหญ่ คุม LED ทุกดวงของบอร์ด แล้วส่งสถานะกลับ 3 · วัดเพดาน 255 ไบต์ ยาวขึ้นทีละ 10 ไบต์ จนหาจุดที่มันเริ่มพัง 4 · ส่งเมื่อเปลี่ยน แทนการส่งตามเวลา วัดว่าลดไปกี่เปอร์เซ็นต์

ข้อ 1 · ฟังทุกอุปกรณ์ด้วย wildcard — subscribe device/+/telemetry แล้วบันทึกว่าแต่ละอุปกรณ์ส่งฟิลด์อะไร (ถ้าเรียนเป็นกลุ่มที่ใช้ CE ตัวเดียวกันจะเห็นของทุกคน ถ้าเรียนคนเดียวให้เทียบกับ payload ของไฟล์ 02_payload_shape.py ในบทเรียน 4.5) ทำตารางเทียบ แล้วเสนอว่า ถ้าทุกอุปกรณ์ต้องใช้ schema เดียวกัน ควรมีฟิลด์ใดบ้างและชื่อว่าอะไร พร้อมเหตุผล

ข้อ 2 · คำสั่งชุดใหญ่ขึ้น — ขยายคำสั่งเป็น {"cmd":"set","led":"RGB_GREEN","on":1} ให้คุม LED ได้ทุกดวงของบอร์ดแยกกัน (gpio.num_leds() ดวง — 3 บน Eva, 5 บน Dev Kit — ระบุดวงด้วยชื่อจาก board_info()["led_names"] ไม่ใช่เลข เพราะเลขเดียวกันคือคนละหลอดบนคนละบอร์ด) และให้บอร์ด publish สถานะกลับ ไปที่ .../status ทุกครั้งที่เปลี่ยน เพื่อให้ฝั่งคอมไม่ต้องเดา

ข้อ 3 · วัดเพดาน 255 ไบต์ด้วยมือตัวเอง — ส่งคำสั่งที่ยาวขึ้นทีละ 10 ไบต์จนโปรแกรมเริ่มพัง บันทึกว่าพังที่ความยาวเท่าไรและอาการเป็นอย่างไร แล้วเขียนวิธีป้องกันที่ดีกว่าการเดา

ข้อ 4 · ส่งเมื่อค่าเปลี่ยน แทนการส่งตามเวลา — เปลี่ยนเงื่อนไขเป็น "ค่าเปลี่ยนเกินเกณฑ์ หรือ ครบ 60 วินาทีแล้วยังไม่ได้ส่ง" แล้ววัดว่าจำนวนข้อความต่อนาทีลดลงกี่เปอร์เซ็นต์ พร้อมอธิบายว่าทำไมยังต้องมีเงื่อนไขข้อหลัง

เขียนคำตอบลงบันทึกการเรียน แล้วอ่านทวนก่อนเริ่มชุดบทเรียนถัดไป

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (1/3)

หน้าจอของ 01_topic_design.py: ออกแบบชื่อ topic ก่อนเขียนโค้ดส่ง หน้าจอของ 02_payload_shape.py: รูปร่างของ payload ตัดสินว่าฝั่งรับทำงานง่ายหรือยาก หน้าจอของ 03_connect_and_publish.py: ต่อ broker แล้วส่งค่าขึ้นไปหนึ่งชุด

01 ออกแบบชื่อ topic ก่อนเขียนโค้ดส่ง · 02 รูปร่างของ payload ตัดสินว่าฝั่งรับทำงานง่ายหรือยาก · 03 ต่อ broker แล้วส่งค่าขึ้นไปหนึ่งชุด
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (2/3)

หน้าจอของ 04_subscribe_command.py: รับคำสั่งจากข้างนอก แล้วทำตาม หน้าจอของ 05_send_every_5s_still_listen.py: ส่งทุก 5 วินาที แต่ยังรับคำสั่งได้ทุก 100 ms หน้าจอของ 06_sent_is_not_delivered.py: publish คืน True แปลว่าอะไร และไม่แปลว่าอะไร

04 รับคำสั่งจากข้างนอก แล้วทำตาม · 05 ส่งทุก 5 วินาที แต่ยังรับคำสั่งได้ทุก 100 ms · 06 publish คืน True แปลว่าอะไร และไม่แปลว่าอะไร
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (3/3)

หน้าจอของ 07_disconnect_frees_id.py: บอกลา broker ให้ถูกวิธี แล้วต่อใหม่ด้วยชื่อเดิมได้ทันที หน้าจอของ 08_real_sensor_leaves_the_board.py: ค่าที่วัดได้จริงบนโต๊ะนี้ ออกไปหาคนอื่น

07 บอกลา broker ให้ถูกวิธี แล้วต่อใหม่ด้วยชื่อเดิมได้ทันที · 08 ค่าที่วัดได้จริงบนโต๊ะนี้ ออกไปหาคนอื่น
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

อ้างอิงและเครดิต

มาตรฐานและเอกสารโพรโทคอล

วิดีโอ

ภาพ (ทุกไฟล์เก็บไว้ในโฟลเดอร์ img/ ของบทเรียน 4.4–4.6 ไม่ได้ลิงก์ข้ามเว็บ) — จาก Wikimedia Commons:

  • s10_mqtt_publish_flow.png (Brivadeneira, CC BY-SA 4.0) · s10_mqtt_topic_wildcards.svg (Ademant, CC BY-SA 4.0) · s10_mqtt_session_flow.svg (Simon A. Eugster, CC BY-SA 4.0)
  • s10_mqtt_publish_packet.svg (Blacktron, CC BY-SA 4.0) · s10_clientserver_sequence.png (Michel Bakni, CC BY-SA 4.0) · s10_pubsub_topic_decoupling.svg (Mathieu.clabaut, CC BY-SA 4.0) · s10_mitm_attack.svg (Miraceti, CC BY-SA 3.0)
  • s10_ce_devices.png, s10_ce_dashboard.png — ภาพหน้าจอ TESAIoT Community Edition v1.1.8, docs/images/screenshots/ (Apache-2.0)

ข้อเท็จจริงของเฟิร์มแวร์และแพลตฟอร์ม

ข้อจำกัดของโมดูล mqtt (ช่องรับ 1 ข้อความ, payload ขาเข้า 255 ไบต์, client_id/username 31 ตัวอักษร, ไม่มี retain, คีย์เวิร์ด keepalive) ตรวจจากซอร์ส modmqtt.c ใน BENTO-TESAIoT-libraries ซึ่งเป็นโค้ดร่วมของทั้ง Eva Kit และ TESAIoT Dev Kit (เพดานทุกตัวจึงเท่ากันสองบอร์ด) · การที่ sensors.init() ถูกปฏิเสธบน Eva Kit และค่าทั้งหมดมาจาก sensors.snapshot() ของคอร์จอ ตรวจจาก modsensors.c (sensors_init() บรรทัด 549 และ sensors_snapshot() บรรทัด 825) · บน Dev Kit snapshot() คืน dict รูปเดียวกัน (IMU อ่านตรงจาก CM33, CapSense/pot จากคอร์จอ) และไม่มีคีย์อุณหภูมิบนบอร์ดไหน — ห้าไฟล์ของบทเรียน 4.1–5.3 จึงอ่านอุณหภูมิผ่าน read_temp() (SHT40 บน Dev Kit / ลูกบิดแทนบน Eva) · ข้อเท็จจริงของ TESAIoT CE (พอร์ต 127.0.0.1:11883, listener 0.0.0.0:1883, reset-mqtt-password, username == client_id == device_id, ACL, การเติม device_id/timestamp และการแบนค่าซ้อนเป็น accel_x) ตรวจจาก repo tesaiot/tesaiot-community-edition v1.3.1 เมื่อ 2026-08-12

ทุกตัวเลขบนสไลด์นี้สืบกลับไปที่ซอร์สโค้ดหรือเอกสารต้นทางได้ ถ้าเจอที่ไม่ตรง แจ้ง erratum ได้เลย

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0