9.19. MQTT ใน Python¶
โมดูล mqtt ที่มาพร้อมกับกล้อง OpenMV ที่รองรับเครือข่ายทุกรุ่นจะห่อหุ้มโปรโตคอล MQTT แบบสายสัญญาณไว้ในคลาสเดียวคือ mqtt.MQTTClient คลาสนี้เปิดซ็อกเก็ต TCP ทำการจับมือ CONNECT บรรจุและแกะแพ็กเก็ตระดับไบต์ จัดการ PINGREQ keepalive และส่งข้อความ PUBLISH ที่เข้ามาไปยังคอลแบ็ก โค้ดแอปพลิเคชันเรียก connect(), publish(), subscribe() และ wait_msg() / check_msg()
9.19.1. ผู้เผยแพร่ในสิบห้าบรรทัด¶
โปรแกรมที่มีประโยชน์ที่เล็กที่สุดคือการเผยแพร่ครั้งเดียว เชื่อมต่อ เผยแพร่ข้อความหนึ่งข้อความ ตัดการเชื่อมต่อ:
from mqtt import MQTTClient
client = MQTTClient(
client_id='yard-cam',
server='test.mosquitto.org',
port=1883,
)
client.connect()
client.publish(b'yard-cam/motion', b'detected at 14:02', qos=0)
client.disconnect()
test.mosquitto.org คือโบรกเกอร์ทดสอบสาธารณะที่ดำเนินการโดยโครงการ Eclipse Mosquitto โบรกเกอร์นี้รับการเชื่อมต่อ TCP ธรรมดาบนพอร์ต 1883 โดยไม่ต้องใช้ข้อมูลรับรอง อย่าใช้สำหรับอะไรที่จริงจัง เพราะไม่มีการรับประกันความเป็นส่วนตัวและเนมสเปซของหัวข้อถูกแชร์กับผู้ทดสอบทุกคนบนอินเทอร์เน็ต
client_id ต้องไม่ซ้ำกันต่อการเชื่อมต่อโบรกเกอร์หนึ่งครั้ง โบรกเกอร์ใช้เพื่อติดตามเซสชัน หัวข้อและเพย์โหลดข้อความเป็นไบต์ ส่ง str หากสะดวกกว่าและไคลเอ็นต์จะเข้ารหัสเป็น UTF-8
9.19.2. การเชื่อมต่อผ่าน TLS¶
สำหรับอะไรก็ตามที่เกินการทดลองเร็วๆ MQTT ผ่าน TLS ต้องการอาร์กิวเมนต์เพิ่มเติมเพียงหนึ่งตัว ดิกชันนารี ssl_params จะถูกส่งต่อไปยัง ssl.wrap_socket() ดังนั้นอะไรก็ตามที่ทำงานที่นั่นก็ทำงานที่นี่ด้วย:
import ssl
client = MQTTClient(
client_id='yard-cam',
server='broker.example.com',
port=8883, # TLS-MQTT default port
ssl_params={'server_hostname': 'broker.example.com'},
user='yard-cam',
password=load_token(),
)
พอร์ต 8883 คือพอร์ต TLS-MQTT ที่สงวนโดย IANA server_hostname เปิดใช้งาน SNI เพื่อให้โบรกเกอร์หลังตัว IP ที่แชร์สามารถนำทางไปยังใบรับรองที่ถูกต้อง ซึ่งเป็นกลไกเดียวกับที่ HTTPS ใช้ user / password แมปกับฟิลด์ชื่อผู้ใช้/รหัสผ่านของแพ็กเก็ต CONNECT โบรกเกอร์จะตัดสินว่าข้อมูลรับรองเหล่านั้นให้สิทธิ์เผยแพร่หรือสมัครรับหัวข้อใดหัวข้อหนึ่งหรือไม่
9.19.3. การสมัครรับและการรับข้อความ¶
ในการรับข้อความ ไคลเอ็นต์จะระบุคอลแบ็กและเรียก subscribe() คอลแบ็กรับอาร์กิวเมนต์ไบต์สองตัวคือหัวข้อและเพย์โหลด:
def on_message(topic, msg):
print('received on', topic.decode(), ':', msg.decode())
client = MQTTClient(
client_id='dashboard',
server='test.mosquitto.org',
port=1883,
callback=on_message,
)
client.connect()
client.subscribe(b'yard-cam/motion', qos=0)
while True:
client.wait_msg()
wait_msg() บล็อกจนกว่าแพ็กเก็ต MQTT หนึ่งตัวมาถึง แยกวิเคราะห์มัน เรียกคอลแบ็กหากเป็น PUBLISH บนหัวข้อที่สมัครรับ และกลับ คอลแบ็กที่สมัครรับจะทำงานจากภายในการเรียกนั้น ไม่มีเธรดพื้นหลัง
สำหรับลูปกล้องแบบโต้ตอบที่ต้องทำงานอื่นต่อไป check_msg() คือลอจิกเดียวกันในรูปแบบที่ไม่บล็อก โดยใช้ select.select() ที่มีการหมดเวลา 50 ms และกลับทันทีหากไม่มีอะไรรอดำเนินการ:
while True:
client.check_msg()
run_frame() # capture + processing
check_motion_threshold()
9.19.4. การเชื่อมต่อใหม่อย่างสะอาด¶
ไคลเอ็นต์ MQTT ที่ทำงานยาวนานทุกตัวต้องจัดการกับการตัดการเชื่อมต่อ Wi-Fi ตัดการเชื่อมต่อ โบรกเกอร์รีสตาร์ท NAT หมดเวลา หรือเพียงแค่ผ่านพ้น keepalive โดยไม่มีทราฟฟิก ล้วนยุติซ็อกเก็ต ไคลเอ็นต์ที่มาพร้อมกับระบบจะส่ง OSError (หรือข้อยกเว้นเปล่าที่มีรหัสตอบกลับของโบรกเกอร์) จากการเรียกที่ตรวจพบการตัดการเชื่อมต่อ และรูปแบบมาตรฐานคือลูปลองใหม่:
import time
def keep_publishing(client, topic, get_message):
while True:
try:
client.connect()
while True:
client.publish(topic, get_message())
time.sleep(5)
except OSError:
print('connection lost, reconnecting in 5s')
time.sleep(5)
การสมัครรับจะ ไม่ ถูกคงอยู่ข้ามการเชื่อมต่อใหม่ เว้นแต่ไคลเอ็นต์ส่ง clean_session=False ตอนเชื่อมต่อ ดังนั้นคำสั่ง connect ภายในควรออก subscribe() ที่จำเป็นใหม่อีกครั้งก่อนเข้าสู่ลูปเผยแพร่
9.19.5. ฮุคการเจตนาสุดท้าย¶
กล้องที่รายงานสถานะควรบอกโบรกเกอร์ว่าจะส่งข้อความใดในนามของกล้องหากการเชื่อมต่อขาดโดยไม่คาดคิด ตั้งค่า will ก่อน connect()
client = MQTTClient(
client_id='yard-cam',
server='broker.example.com',
port=8883,
ssl_params={'server_hostname': 'broker.example.com'},
)
client.set_last_will(
b'yard-cam/status',
b'offline',
retain=True,
qos=0,
)
client.connect()
client.publish(b'yard-cam/status', b'online', retain=True)
ตอนนี้แดชบอร์ดใดๆ ที่สมัครรับ yard-cam/status จะเห็น online ในทันทีที่กล้องเชื่อมต่อ และ offline เมื่อใดก็ตามที่โบรกเกอร์ตรวจพบว่ากล้องตัดการเชื่อมต่อ ข้อความ offline ที่ถูกเก็บไว้จะคงอยู่บนโบรกเกอร์เพื่อให้แดชบอร์ดที่เชื่อมต่อสิบนาทีต่อมายังคงเห็นสถานะปัจจุบันที่ถูกต้อง
9.19.6. เมื่อใดควรเลือก MQTT แทน HTTP¶
บทเกี่ยวกับเว็บเซิร์ฟเวอร์ครอบคลุมกล้องที่ทำหน้าที่เป็นเซิร์ฟเวอร์ HTTP และในหน้าอัปโหลดคลาวด์ ทำหน้าที่เป็นไคลเอ็นต์ HTTP ที่โพสต์ JPEG ไปยัง URL คงที่ ทั้งสองมีที่ใช้ของตัวเอง เวลาที่เหมาะสมที่จะใช้ MQTT แทน:
ข้อมูลเดิมต้องส่งไปยังผู้ฟังหลายคน (แดชบอร์ด บริการแจ้งเตือน เครื่องบันทึก) โดยที่กล้องไม่รู้รายชื่อล่วงหน้า
ผู้ฟังอาจมาและไปโดยที่กล้องไม่ต้องรีสตาร์ท
กล้องต้องการ สมัครรับ เพื่อรับคำสั่งจากตัวควบคุม ซึ่งไคลเอ็นต์ HTTP ไม่สามารถทำได้โดยไม่ใช้การสำรวจยาวหรือเซิร์ฟเวอร์ที่ส่งข้อมูลไปยัง URL คอลแบ็ก
การเชื่อมต่อต้องอยู่รอดในช่วงว่างนานๆ ได้อย่างประหยัด
เวลาที่เหมาะสมที่จะใช้ HTTP ต่อไป: กล้องหนึ่งตัว เซิร์ฟเวอร์หนึ่งตัว รูปแบบคำขอ/การตอบสนองคงที่พร้อมเนื้อหาที่ใหญ่เกินไปสำหรับหัวข้อ MQTT เดียว (JPEG ผ่าน MQTT ทำงานได้แต่ไม่เป็นมิตรกับโบรกเกอร์ HTTP POST เหมาะสมกว่า)
การอ้างอิงข้าม: หน้าอัปโหลดคลาวด์ในบทเว็บเซิร์ฟเวอร์แสดงเวอร์ชัน HTTP ของ "กล้อง → คลังเก็บข้อมูลคลาวด์" เวอร์ชัน MQTT ของปัญหาเดิมช่วยให้กล้องไม่ผูกติดกับ URL ของคลังเก็บข้อมูล และช่วยให้ผู้บริโภคคนที่สอง (เช่น แอปแจ้งเตือนโทรศัพท์) เชื่อมต่อกับสตรีมเดียวกันได้