
บอร์ด TX อ่านความเอียงแล้ว publish ผ่าน broker · บอร์ด RX subscribe แล้วขยับยานตาม — ทั้งระบบทำงานผ่านอากาศ ไม่มีสายต่อระหว่างสองบอร์ดเลย
ปี 2006 Nintendo เปลี่ยนวิธีเล่นเกมไปทั้งวงการด้วยจอยที่มี accelerometer ข้างใน — เอียงมือ ฟันดาบ ตีเทนนิส ของบนจอขยับตาม

ที่มา: "Wii Remote Image" — Greyson Orlando, Public domain, Wikimedia Commons · จอยที่จุดกระแสเกมควบคุมด้วยการเคลื่อนไหว
เราไม่ได้สร้างของเล่นใหม่จากศูนย์ เรากำลัง ถอดหลักการ ของอุปกรณ์ระดับตำนานออกมาทำเองด้วยบอร์ดสองใบ
ดูของจริงสักครู่: สังเกตว่าผู้เล่นแค่ "เหวี่ยงมือ" แล้วไม้เทนนิสบนจอขยับตาม — นั่นคือ accelerometer ในจอยอ่านความเอียง/ความเร่ง แล้วส่งไปให้เครื่องเกม เหมือนที่เราจะทำเป๊ะ
Wii Sports Nintendo Wii Gameplay Walkthrough Part 1 - Tennis! — Zebra Gamer · เอียง/เหวี่ยงมือ -> ของบนจอขยับตาม คือสิ่งที่โปรเจกต์นี้จำลอง
ก่อนส่ง เราต้องเข้าใจว่าค่า ax, ay มาจากไหน เวลาเราเอียงบอร์ดเป็นมุม θ แรงโน้มถ่วง g ที่ดึงลงตรง ๆ ถูกแตกออกตามมุมที่เอียง

GAIN แล้วส่งออกไปค่า ax สูงสุดแค่ราว ๆ 1.0 เพราะมันคือ ค่า sin ของมุมเอียง ไม่ใช่ความเร็วของบอร์ด
ค่า เป็นทศนิยมต่อเนื่อง (เช่น 0.4731...) ส่งทั้งดุ้นก็เปลือง เราจึงบีบให้เป็น จำนวนเต็มเล็ก ๆ ก่อน ด้วยสองขั้น: ตัด deadzone แล้วคูณ GAIN ปัดเป็น int

int() ปัดลงเป็นจำนวนเต็มpayload แล้ว publish ออกไปทำไมต้องปัดเป็น int ก่อนส่ง เพราะ payload เราเป็น ข้อความสั้น ๆ (
"x:5,y:-3") เลขเต็มอ่านง่าย ส่งน้อยไบต์ และฝั่ง RXint()กลับมาตรง ๆ ได้เลย
บอร์ด TX ส่ง "โปสการ์ดความเอียง" ผ่าน broker ไปให้บอร์ด RX ที่ฟัง topic เดียวกัน
สัญญาของเรา: topic = ctrl/ship, payload = "x:<int>,y:<int>" เช่น "x:-3,y:2" ทั้งสองฝั่งต้องเป๊ะตัวเดียวกัน

publish โปสการ์ดเข้า broker แล้ว จบหน้าที่ ไปอ่านเอียงรอบใหม่ทันทีสังเกตว่า TX ไม่เคย "รอ" RX ตอบ — ยิงแล้วไปต่อเลย (fire-and-forget) นี่คือเหตุผลที่ระบบ IoT ส่งจากเซนเซอร์เป็นพัน ๆ ตัวพร้อมกันได้โดยไม่ติดขัด
หัวใจฝั่งส่ง: อ่านความเอียง ตัด deadzone แปลงเป็นเลขเต็ม แล้ว publish ทุก ~100ms
sensors.init() # ปลุกเซนเซอร์ครั้งเดียวพอ
while not btn.is_pressed(): # กด SW1 ค้างเพื่อหยุด
ax, ay, az = sensors.bmi270.acceleration() # เซนเซอร์คืน m/s²
ax, ay = ax/9.81, ay/9.81 # แปลงเป็น g (วางราบ ≈ 0, เอียงราว -1..1)
if abs(ax) < DEADZONE: # ตัด noise -> ยานนิ่งเมื่อวางราบ
ax = 0.0
if abs(ay) < DEADZONE:
ay = 0.0
x = int(ax * GAIN) # แปลง g (~ -1..1) เป็นเลขเต็ม -10..10
y = int(ay * GAIN)
payload = "x:%d,y:%d" % (x, y) # รูปแบบที่ตกลงกับ RX: "x:-3,y:2"
mqtt.publish(TOPIC, payload) # ส่งออกไปให้บอร์ด RX
time.sleep_ms(100) # ~10 ครั้ง/วินาที พอลื่น ไม่ถี่เกิน
deadzone สำคัญมากนะ ถ้าไม่ตัด ยานจะสั่นตลอดเพราะมือเราไม่เคยนิ่งจริง
โค้ดข้างบนคือ "สายพาน" สั้น ๆ ที่ค่อย ๆ แปลงค่าเซนเซอร์ดิบให้กลายเป็นข้อความพร้อมส่ง:

แต่ละกล่อง = หนึ่งบรรทัดในลูป TX · ขั้นตอนนี้ (อ่านดิบ -> ทำให้เป็นหน่วยมาตรฐาน -> กรอง -> ย่อ -> ห่อเป็นข้อความ) คือลำดับเดียวกับที่ระบบ telemetry จริงทำก่อนยิงค่าขึ้น cloud ทุกครั้ง
จำลำดับนี้ไว้ มันใช้ซ้ำได้กับเซนเซอร์ทุกตัว: อ่านค่าดิบ → แปลงหน่วย → ตัด noise → บีบขนาด → ห่อเป็น payload → ส่ง
ฝั่งรับใช้เกมลูป: ทุกเฟรมดึงข้อความล่าสุดมา parse แล้วขยับยาน
def parse(payload):
text = payload.decode() # bytes ต้อง decode ก่อนเสมอ
x = y = 0
for part in text.split(","): # ["x:-3", "y:2"]
key, value = part.split(":") # ["x", "-3"]
if key == "x":
x = int(value)
elif key == "y":
y = int(value)
return x, y
def update(): # เกมลูปเรียกทุกเฟรม
msg = mqtt.get_message() # (topic, bytes) หรือ None (ไม่บล็อก)
if msg is not None:
topic, data = msg
x, y = parse(data)
ship.move(x, y) # move คุมขอบจอให้แล้ว
game.run(update, fps=20) # BACK = ออกจากเกม
ฟังก์ชัน parse() คือหัวใจ — มันแกะข้อความดิบทีละชั้นจนได้ตัวเลขสองตัว:

get_message()คืนNoneเมื่อยังไม่มีของ เกมจึงไม่ค้าง วนเช็กไปเรื่อย ๆ ได้สบาย
ตั้ง SSID/PASSWORD ให้ตรงห้องเรา (หรือต่อ WiFi ผ่านเมนูบนจอไว้ก่อน) แล้วแยกบอร์ด — ผลที่ควรเห็น: เอียงบอร์ดซ้าย ยานวิ่งซ้าย เอียงขวา ยานวิ่งขวา แบบนี้
codes/ex_a3_remote_tx.pycodes/ex_a3_remote_rx.pyเคล็ดลับลำดับ: เปิด RX ก่อนให้พร้อมรับ แล้วค่อยเปิด TX จะได้เห็นยานขยับทันทีที่เริ่มเอียง · ทั้งสองฝั่งต้องใช้ TOPIC = "ctrl/ship" ตัวเดียวกัน และตั้ง CLIENT คนละชื่อ
จอยรีโมตวันนี้ดูเหมือนของเล่น แต่ข้างใต้คือรากฐานที่ระบบ IoT จริงใช้ทุกวัน:

ฝั่ง Embedded / ฮาร์ดแวร์
0x68ฝั่ง Python
update ให้ game.run(update, fps=20) เรียกแทนเราทุกเฟรม (callback)payload.decode() แล้ว split(",") / split(":") แปลง bytes กลับเป็นตัวเลขฝั่ง Algorithms
int() คือการแปลงค่าเซนเซอร์มาตรฐานsleep_ms(100) คุมอัตราส่ง ~10 Hz เหมือน control loop ในงานจริง (ถี่ไปท่วม broker, ห่างไปยานกระตุก)ฝั่ง Graphics
box.move(dx,dy) ขยับยานแล้วคุมไม่ให้หลุดขอบจอ รากฐานของการวาดวัตถุบนจอ 2Dดูคลิป (เสริม): กลไก publish/subscribe ที่ทั้งโปรเจกต์นี้ตั้งอยู่ จากทีมที่เขียนสเปก MQTT เอง
MQTT Publish / Subscribe / Unsubscribe | MQTT Essentials Part 5 — HiveMQ
สิ่งที่เราทำวันนี้คือต้นแบบจริงของหลายระบบรอบตัว ลองเลือกต่อยอดสักอย่าง:
"x:-3,y:2,fire:1" แล้ว RX ยิงกระสุนตามคำสั่ง — ได้เกมยิงแบบคุมจากมืออีกบอร์ด
ของจริง: รถ RC ต่อเข้าไมโครคอนโทรลเลอร์ (Arduino) — เปลี่ยน "ขยับยานบนจอ" เป็น "สั่งล้อหมุนจริง" ก็คือ IoT remote · ที่มา: "RC car hooked up to the arduino" — Mbroemme5783, CC BY-SA 3.0, Wikimedia Commons

หลักการเดียวกันนี้ scale ได้ถึงระดับโรงงาน เซนเซอร์หนึ่งที่ ส่งค่าให้อีกที่ตัดสินใจ คือหัวใจของ IoT ทั้งหมด น้อง ๆ ทำเป็นแล้ววันนี้
fit-css