Bir MQTT cihaz simülatörü, bir broker'ı, backend'i veya dashboard'u henüz var olmayan bir filoya karşı test etmenizi sağlar. İşin püf noktası her sanal cihazın gerçek bir cihaz gibi davranmasıdır: kendi bağlantısı ve benzersiz client ID'si, onu çevrimdışı olarak işaretleyen bir Last Will, hafifçe düzensiz aralıklarla telemetri, yanıt verdiği komutlar ve ara sıra bir elektrik kesintisi.
Bu rehber böyle bir simülatörü asyncio ve aiomqtt ile yaklaşık 120 satır Python'da kuruyor, 1.000 cihaza ölçeklendiriyor ve broker'ın ne gördüğünü nasıl doğrulayacağınızı gösteriyor.
Gerçekçi bir sanal cihaz ne yapar
| Davranış | Test açısından neden önemli |
|---|---|
| Kendi bağlantısı, benzersiz client ID | İstemci bazlı oturumları, ACL'leri ve bağlantı limitlerini çalıştırır. Aynı ID'ye sahip istemciler birbirini düşürür. |
| Kademeli açılış | Gerçek filolar aynı milisaniyede bağlanmaz — bir kesintiden sonra elektrik geri geldiğinde hariç. |
Retained online durumu + Last Will offline | Dashboard'ların varlık bilgisi göstermesini sağlar ve broker'ın will'leri tetiklediğini test eder. |
| Jitter'lı publish aralığı | Gerçek cihazların asla üretmediği kusursuz senkronize patlamaları önler. |
| Heterojen filo | Bazı cihazlar 5 sn'de, bazıları 30 sn'de bir raporlar; payload'lar farklıdır. |
| Komut topic'i | Aşağı yönlü yolu test eder: backend → broker → cihaz → acknowledgement. |
| Elektrik kesintisi arızaları | Cihaz DISCONNECT göndermeden kaybolur, sonra yeniden başlar ve backoff ile tekrar bağlanır. |
Topic yapısı
Her cihaz küçük bir alt ağaca sahiptir. Çalıştırma başına bir önek, paralel test çalıştırmalarını birbirinden ayırır:
| Topic | Yön | Retained | İçerik |
|---|---|---|---|
testmqtt/sim/<id>/status | cihaz → broker | evet | online veya Last Will ile offline |
testmqtt/sim/<id>/telemetry | cihaz → broker | hayır | JSON ölçüm |
testmqtt/sim/<id>/cmd | backend → cihaz | hayır | JSON komut, örn. {"interval": 2} |
testmqtt/sim/<id>/cmd/ack | cihaz → backend | hayır | Onay |
Client ID'ler sim-<run>-<index> kalıbını izler, örneğin sim-3fa2c1-0042. Benzersizdirler, broker loglarında okunabilirler ve kolayca filtrelenirler. Client ID üretici ve client ID en iyi uygulamaları bu özelliklerin neden önemli olduğunu açıklıyor.
Simülatör
Bağımlılığı pip install aiomqtt ile kurun (paho-mqtt 2.x üzerine kurulu 2.x sürümü). Her cihaz kendi aiomqtt.Client nesnesine sahip bir coroutine'dir:
import asyncio, json, os, random, socket, sys, time, uuid
from collections import Counter
import aiomqtt
HOST, PORT = os.environ.get("MQTT_HOST", "broker.connectmq.com"), 1883
USER, PASS = os.environ.get("MQTT_USER"), os.environ.get("MQTT_PASS")
N_DEVICES = int(os.environ.get("DEVICES", 100))
RAMP_S = 60 # spread device boot over one minute
CRASH_PROB = 0.002 # chance of a power loss after each publish
RUN = uuid.uuid4().hex[:6]
stats = Counter()
class PowerLoss(Exception):
pass
async def commands(client, base, state):
try:
async for msg in client.messages:
try:
cmd = dict(json.loads(msg.payload))
if "interval" in cmd:
state["interval"] = max(1.0, float(cmd["interval"]))
except (ValueError, TypeError):
continue # ignore malformed commands
await client.publish(f"{base}/cmd/ack", json.dumps({"ok": True, **cmd}), qos=1)
stats["commands"] += 1
except aiomqtt.MqttError:
pass # connection lost; device() reconnects
async def device(i: int):
dev_id = f"sim-{RUN}-{i:04d}"
base = f"testmqtt/sim/{dev_id}"
rng = random.Random(i)
state = {"interval": rng.choice([5, 10, 30])}
temp, boots, attempt = rng.uniform(19, 24), 0, 0
await asyncio.sleep(rng.uniform(0, RAMP_S)) # staggered boot
while True:
try:
will = aiomqtt.Will(f"{base}/status", b"offline", qos=1, retain=True)
async with aiomqtt.Client(HOST, PORT, identifier=dev_id, username=USER,
password=PASS, keepalive=30, will=will) as client:
attempt, boots, started = 0, boots + 1, time.monotonic()
stats["online"] += 1
try:
await client.publish(f"{base}/status", b"online", qos=1, retain=True)
await client.subscribe(f"{base}/cmd", qos=1)
listener = asyncio.create_task(commands(client, base, state))
try:
while True:
temp += rng.gauss(0, 0.05)
reading = {"temp": round(temp, 2), "boots": boots,
"uptime": int(time.monotonic() - started), "fw": "1.4.2"}
await client.publish(f"{base}/telemetry", json.dumps(reading), qos=1)
stats["published"] += 1
if rng.random() < CRASH_PROB:
# power loss: drop TCP without DISCONNECT so the broker fires the will
# (aiomqtt has no public API for this, so reach the paho client)
client._client.socket().shutdown(socket.SHUT_RDWR)
await asyncio.sleep(1)
raise PowerLoss
await asyncio.sleep(state["interval"] * rng.uniform(0.9, 1.1))
finally:
listener.cancel()
finally:
stats["online"] -= 1
except PowerLoss:
stats["power_losses"] += 1
await asyncio.sleep(rng.uniform(5, 30)) # time to reboot
except aiomqtt.MqttError:
stats["conn_errors"] += 1
attempt += 1
await asyncio.sleep(min(60, 2 ** attempt) * rng.uniform(0.5, 1.0))
async def report():
while True:
await asyncio.sleep(10)
print(time.strftime("%H:%M:%S"), dict(stats), flush=True)
async def main():
print(f"run {RUN}: starting {N_DEVICES} devices under testmqtt/sim/sim-{RUN}-*")
await asyncio.gather(report(), *(device(i) for i in range(N_DEVICES)))
if __name__ == "__main__":
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())Birkaç ayrıntı bu simülatörü güvenilir kılar:
- Cihaz başına
random.Random(i)— 42 numaralı cihaz her zaman aynı aralığa, aynı başlangıç sıcaklığına ve aynı arıza desenine sahiptir; bir kez gördüğünüz bir hata yeniden üretilebilir. - Eşleşen Last Will ile retained durum — cihaz bağlandıktan sonra
onlinepublish eder; kaybolursa broker retainedofflinewill'ini publish eder. Will'lerin tam olarak ne zaman tetiklendiği için MQTT Last Will and Testament yazısını okuyun. - Üstel backoff ve jitter ile yeniden bağlanma — bu olmadan, broker'ı aynı anda kaybeden 1.000 cihaz yine aynı anda geri döner. Bu desen ve nasıl önleneceği MQTT yeniden bağlanma fırtınaları yazısında anlatılıyor.
- Windows — aiomqtt orada selector tabanlı event loop'a ihtiyaç duyar; en alttaki policy satırı bu yüzden var.
1.000 cihaz çalıştırmak
ulimit -n 4096 # one socket per device, plus headroom
export MQTT_HOST=your-broker-host # your own broker instance for the full fleet
export MQTT_USER=username MQTT_PASS=password
DEVICES=1000 python simulator.pySimülatör her on saniyede bir {'online': 1000, 'published': 48213, 'power_losses': 3, ...} gibi bir satır yazdırır. Varsayılan aralıklarla (cihaz başına 5, 10 veya 30 sn), 1.000 cihaz saniyede 100 mesaj mertebesinde trafik üretir — bir benchmark değil, gerçekçi telemetri. Daha fazla zorlamak için aralıkları kısaltın veya farklı RUN önekleriyle daha fazla süreç başlatın; saf bağlantı baskısı için MQTT bağlantı stres testi yazısına bakın.
Broker'ın ne gördüğünü doğrulayın
Broker'ın şu anda çevrimiçi olarak gösterdiği cihazları sayın — retained durum mesajları subscribe olur olmaz teslim edilir:
mosquitto_sub -h broker.connectmq.com -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" \
-t 'testmqtt/sim/+/status' -v -W 5 | grep -c ' online$'Aynı filtreye çevrimiçi MQTT istemcisinde subscribe olup bir elektrik kesintisini bekleyerek will'lerin gerçek zamanlı tetiklendiğini izleyin: cihazın durumu offline olur, ardından simüle edilen yeniden başlatmadan sonra tekrar online olur.
Bir cihaza komut gönderip onayı bekleyerek aşağı yönlü yolu test edin:
mosquitto_sub -h broker.connectmq.com -u "$MQTT_USER" -P "$MQTT_PASS" \
-t 'testmqtt/sim/sim-3fa2c1-0042/cmd/ack' -C 1 -v &
mosquitto_pub -h broker.connectmq.com -u "$MQTT_USER" -P "$MQTT_PASS" \
-t 'testmqtt/sim/sim-3fa2c1-0042/cmd' -q 1 -m '{"interval": 2}'3fa2c1 değerini simülatörün başlangıçta yazdırdığı çalıştırma ID'siyle değiştirin. Onaydan sonra o cihazın telemetrisi iki saniyede bir gelmelidir.
Simülatörü genişletmek
- Gerçekçi payload'lar — tek sıcaklık değerini günlük döngü, kayma ve binary kodlama içeren MQTT test verisi üreticisi sensör modeliyle değiştirin.
- TLS — açılıştaki TLS handshake'lerinin ek maliyetini ölçmek için
aiomqtt.Client'aport=8883vetls_params=aiomqtt.TLSParameters()verin. - MQTT 5 — kalıcı oturumları ve mesaj süresinin dolmasını test etmek için
protocol=aiomqtt.ProtocolVersion.V5ve session expiry özelliklerini ekleyin. - Cihaz tipleri — aynı
main()içinden farklı topic ve hızlara sahip birkaç coroutine fonksiyonu (thermostat,meter,gateway) çalıştırın. - Toplu yeniden bağlanma —
CRASH_PROBdeğerini sıfıra çekin ve bir çalıştırma sırasında broker'ı yeniden başlatarak tüm filonun nasıl toparlandığını görün.
Simüle cihazların çevrimiçi oluşunu izleyin
Bir simülatör "ölçeklendiğini düşünüyoruz" cümlesini her hafta yeniden çalıştırabileceğiniz bir deneye dönüştürür. Her sanal cihaza gerçek bir kimlik verin, onu donanımın arızalandığı gibi arızalandırın ve sonucu broker üzerinde doğrulayın — böylece ilk bin gerçek cihaz, zaten geçtiğiniz bir testin tekrarından ibaret olur.
Sıkça sorulan sorular
Tek bir makine kaç simüle MQTT cihazı çalıştırabilir?
asyncio ile tek bir Python süreci, tipik telemetri hızlarında yüzlerce ila birkaç bin cihazı rahatça çalıştırır, çünkü her cihaz bir thread değil bir coroutine'dir. Pratik sınırlar dosya tanımlayıcı limiti (ulimit -n), broker'ın bağlantı limitleri ve JSON ile TLS'e harcanan CPU'dur. Bunun ötesinde birden fazla süreç veya makine kullanın.
Her simüle cihazın kendi MQTT bağlantısı olmalı mı?
Evet, broker'ı gerçek bir filonun kullandığı şekilde test etmek istiyorsanız. Cihaz başına bir bağlantı; bağlantı yönetimini, keep-alive'ı, oturumları, Last Will'i ve istemci bazlı ACL'leri çalıştırır. Birçok cihaz adına tek bir bağlantı üzerinden publish etmek yalnızca throughput'u test eder.
Bir cihazın elektriğinin kesilmesini nasıl simüle ederim?
TCP bağlantısını DISCONNECT göndermeden kapatın. Broker bunu beklenmedik bir kopma olarak değerlendirir ve cihazın Last Will mesajını publish eder. Temiz bir disconnect ise will'i iptal eder, dolayısıyla çevrimdışı tespitinizi test etmez.



