IoT Eğitimleri

Python ile MQTT Verisini SQLite'a Kaydetme: Sensör Mesajlarını Saklayın ve Sorgulayın

Her MQTT mesajını zaman damgası, topic, QoS ve retain bayrağıyla SQLite'a kaydeden bir Python subscriber oluşturun; toplu yazma ve WAL modu kullanın, ardından geçmişi, saatlik ortalamaları ve en son değerleri düz SQL ile sorgulayın.

Zaman damgalı sensör mesajları tablosuyla python mqtt sqlite veri aktarımı yapan betik
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Python ile MQTT verisini SQLite'a göndermek, IoT projenize bir geçmiş kazandırmanın en basit yoludur: tek bir dosya, veritabanı sunucusu yok ve sorgular için tam SQL. Bu eğitimde broker.connectmq.com üzerinden gelen her mesajı zaman damgası, topic ve bayraklarıyla saklayan, verimli toplu yazmalar yapan ve yeniden bağlanmalara dayanan bir paho-mqtt subscriber oluşturacaksınız. Ardından verileri son değerler, saatlik ortalamalar ve saklama süresi temizliği için sorgulayacaksınız.

MQTT Verisi için Neden SQLite?

Bir MQTT broker, mesajları o anda subscribe olan kişiye iletir. Bir veritabanı değildir ve topic başına bir retained mesaj dışında geçmiş tutmaz. "Gece saat 3'te sıcaklık neydi?" sorusunu yanıtlamak istiyorsanız, bir şeyin subscribe olup veriyi saklaması gerekir.

SQLite, bu iş için mükemmel bir ilk tercihtir:

İhtiyaç SQLite bunu nasıl karşılar
Sıfır kurulum Python'un standart kütüphanesinde yerleşik (sqlite3)
Tek dosya Yedeklemesi, kopyalaması veya bir GUI tarayıcıda açması kolay
SQL sorguları Filtreleme, gruplama, JSON fonksiyonları, tarih fonksiyonları
Orta düzey yazma hızları Transaction'lar içinde toplu yapıldığında saniyede binlerce insert
Edge cihazlar Bir Raspberry Pi veya endüstriyel PC'de rahatça çalışır

Farklı makinelerdeki çok sayıda eş zamanlı yazıcı veya bir cihaz filosundan gelen yıllarca yüksek frekanslı veri için doğru araç değildir. Bu durumlar için MQTT'den TimescaleDB'ye rehberine veya MQTT verisini InfluxDB'de saklama yazısına bakın. Tek bir saha, bir gateway veya bir prototip için SQLite'ı geçmek zordur.

Mimari: Ağ Thread'ini Serbest Tutun

Saf yaklaşım, on_message içinde bir INSERT ve bir commit() çalıştırır. Bu dakikada birkaç mesajda işe yarar, ancak her commit bir disk senkronizasyonunu zorlar. Daha yüksek hızlarda callback geride kalır, paho'nun network loop'u takılır ve keepalive ping'leri broker'ın bağlantıyı düşürmesine yetecek kadar gecikebilir.

Sağlam tasarım iki işi birbirinden ayırır:

  1. paho, network loop'unu bir arka plan thread'inde çalıştırır (loop_start()).
  2. on_message mesajı bir queue.Queue içine koymaktan başka bir şey yapmaz.
  3. SQLite bağlantısının sahibi olan ana thread mesajları kuyruktan çeker ve toplu olarak yazar; her toplu işte veya saniyede bir kez commit eder.

Bu aynı zamanda SQLite'ın, bir bağlantının onu oluşturan thread'den kullanılması gerektiği kuralını da aşmanızı sağlar.

Adım 1: Kurulum ve Hazırlık

Yalnızca paho-mqtt gerekir; sqlite3 Python ile birlikte gelir:

python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install "paho-mqtt>=2.0"

Verilerinizin herkese açık MQTT test broker'ının diğer kullanıcılarının verileriyle karışmaması için rastgele bir topic öneki seçin. Bu eğitim connectmq/demo/k7f3q2 kullanır.

Adım 2: Python ile MQTT'den SQLite'a Logger

Bunu mqtt_sqlite_logger.py olarak kaydedin:

import queue
import signal
import sqlite3
import time
import uuid
from datetime import datetime, timezone

import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
PORT = 1883
TOPIC = "connectmq/demo/k7f3q2/#"
DB_PATH = "mqtt_messages.db"
BATCH_SIZE = 200
FLUSH_INTERVAL = 1.0  # saniye

SCHEMA = """
CREATE TABLE IF NOT EXISTS messages (
    id          INTEGER PRIMARY KEY,
    received_at TEXT    NOT NULL,
    topic       TEXT    NOT NULL,
    payload     TEXT,
    qos         INTEGER NOT NULL,
    retained    INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_messages_topic_time ON messages (topic, received_at);
CREATE INDEX IF NOT EXISTS idx_messages_time ON messages (received_at);
"""

inbox: "queue.Queue[tuple]" = queue.Queue(maxsize=50_000)
running = True


def utc_now() -> str:
    # 2026-10-16T08:15:30.123Z, sıralanabilir ve SQLite tarih fonksiyonlarınca anlaşılır
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connect failed: {reason_code}")
        return
    print(f"Connected, subscribing to {TOPIC}")
    client.subscribe(TOPIC, qos=1)


def on_message(client, userdata, msg):
    row = (
        utc_now(),
        msg.topic,
        msg.payload.decode("utf-8", errors="replace"),
        msg.qos,
        int(msg.retain),
    )
    try:
        inbox.put_nowait(row)
    except queue.Full:
        print("Queue full, dropping message")  # veritabanı yetişemiyor


def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
    print(f"Disconnected: {reason_code}, paho will reconnect")


def open_db(path: str) -> sqlite3.Connection:
    db = sqlite3.connect(path)
    db.execute("PRAGMA journal_mode=WAL")    # okuyucular yazıcıyı bloklamaz
    db.execute("PRAGMA synchronous=NORMAL")  # WAL ile güvenli, çok daha hızlı
    db.executescript(SCHEMA)
    return db


def flush(db: sqlite3.Connection, batch: list) -> None:
    if not batch:
        return
    with db:  # toplu iş başına bir transaction
        db.executemany(
            "INSERT INTO messages (received_at, topic, payload, qos, retained) VALUES (?, ?, ?, ?, ?)",
            batch,
        )
    print(f"Stored {len(batch)} messages")
    batch.clear()


def stop(*_):
    global running
    running = False


def main():
    signal.signal(signal.SIGINT, stop)
    signal.signal(signal.SIGTERM, stop)

    db = open_db(DB_PATH)

    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"sqlite-{uuid.uuid4().hex[:8]}")
    # Özel ConnectMQ broker:
    # client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
    # client.tls_set(); ve 8883 portunu kullanın
    client.on_connect = on_connect
    client.on_message = on_message
    client.on_disconnect = on_disconnect
    client.reconnect_delay_set(min_delay=1, max_delay=60)
    client.connect_async(BROKER, PORT, keepalive=60)
    client.loop_start()

    batch: list = []
    last_flush = time.monotonic()
    try:
        while running:
            try:
                batch.append(inbox.get(timeout=0.2))
            except queue.Empty:
                pass
            if len(batch) >= BATCH_SIZE or time.monotonic() - last_flush >= FLUSH_INTERVAL:
                flush(db, batch)
                last_flush = time.monotonic()
    finally:
        client.disconnect()
        client.loop_stop()
        while not inbox.empty():
            batch.append(inbox.get_nowait())
        flush(db, batch)
        db.close()
        print("Logger stopped cleanly")


if __name__ == "__main__":
    main()

python mqtt_sqlite_logger.py ile çalıştırın. Başka bir terminalde biraz test verisi publish edin:

for i in $(seq 1 20); do
  mosquitto_pub -h broker.connectmq.com -p 1883 -i sqlite-test-pub \
    -t "connectmq/demo/k7f3q2/sensors/room$((i % 2 + 1))" \
    -m "{\"temperature\": $((20 + RANDOM % 5)).$((RANDOM % 10)), \"humidity\": $((40 + RANDOM % 10))}"
  sleep 1
done

Logger yaklaşık saniyede bir "Stored 1 messages" yazdırır. Ctrl+C ile durdurun; kuyrukta kalanlar çıkıştan önce yazılır.

Logger Nasıl Güvenilir Kalıyor

Zaman damgaları. Logger, mesajın alındığı anı milisaniye hassasiyetinde UTC olarak kaydeder. Cihaz saatleri kayar ve bazı cihazların saati hiç yoktur; bu yüzden alınma zaman damgası güvenilir bir temel çizgidir. Payload'ınız kendi zaman damgasını taşıyorsa ikisini birden saklayın. Python MQTT JSON rehberi publish tarafında nasıl zaman damgası ekleneceğini gösterir.

WAL modu. Write-ahead logging, logger yazarken bir panelin veya sqlite3 CLI'ın veritabanını okumasına izin verir. Bu olmadan uzun okumalar insert'leri bloklayabilir.

Sınırlı kuyruk. Disk takılırsa kuyruk 50.000 mesaja kadar büyür, sonrasında belleği tüketmek yerine yeni mesajları bir uyarıyla düşürür. Boyutu trafiğinize göre ayarlayın.

Yeniden bağlanmalar. connect_async() ile loop_start() arka planda yeniden denemeyi sürdürür ve abonelik on_connect içinde geri yüklenir. Logger çevrimdışıyken publish edilen mesajlar, kalıcı bir oturum kullanmadığınız sürece kaybolur. MQTT 5 ile bir session expiry interval ve clean_start=False ayarlayarak broker'ın QoS 1 mesajlarını sizin için kuyruğa almasını sağlayabilirsiniz; herkese açık broker oturumları 30 dakikaya kadar tutar. paho-mqtt yeniden bağlanma rehberi ayrıntıları açıklar.

Retained mesajlar. Her yeniden bağlanmada broker retained mesajları tekrar gönderir; bu yüzden onların birden fazla kez saklandığını görürsünüz. retained sütunu bunları analizden filtrelemenizi sağlar.

Adım 3: MQTT Geçmişinizi Sorgulayın

Veritabanını SQLite CLI (sqlite3 mqtt_messages.db) veya herhangi bir SQLite tarayıcısıyla açın ve şu sorguları deneyin.

Bir topic için son 10 mesaj:

SELECT received_at, payload
FROM messages
WHERE topic = 'connectmq/demo/k7f3q2/sensors/room1'
ORDER BY received_at DESC
LIMIT 10;

SQLite'ın MAX() ile çıplak sütunlar için belgelenmiş davranışını kullanarak topic başına en son değer:

SELECT topic, payload, MAX(received_at) AS last_seen
FROM messages
WHERE retained = 0
GROUP BY topic;

SQLite'ın yerleşik JSON fonksiyonlarını kullanarak JSON payload'lardan saatlik ortalamalar:

SELECT strftime('%Y-%m-%d %H:00', received_at)            AS hour,
       topic,
       ROUND(AVG(json_extract(payload, '$.temperature')), 2) AS avg_temperature,
       COUNT(*)                                             AS samples
FROM messages
WHERE topic LIKE 'connectmq/demo/k7f3q2/sensors/%'
  AND json_valid(payload)
GROUP BY hour, topic
ORDER BY hour;

Yalnızca son yedi günü tutun ve alanı geri kazanın:

DELETE FROM messages
WHERE received_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days');
VACUUM;

received_at sabit bir ISO 8601 biçiminde saklandığından, düz dizgi karşılaştırması doğru sıralar ve zaman aralığı filtresi index'i kullanabilir. Saklama sorgusunu cron'dan veya günde bir kez logger'ın kendisinden çalıştırabilirsiniz.

Verilerle Python'da çalışmak için pandas.read_sql_query("SELECT ...", sqlite3.connect("mqtt_messages.db")) bir DataFrame döndürür. MQTT ve pandas eğitimi analiz ve yeniden örneklemeyi ele alır.

Sorun Giderme Kontrol Listesi

  1. "database is locked": başka bir işlem bir yazma transaction'ı tutuyor. WAL modunun etkin olduğundan ve dosyaya yalnızca bir logger'ın yazdığından emin olun.
  2. Hiçbir şey saklanmıyor: topic filtresinin publisher'ın topic'iyle eşleştiğini doğrulayın; mosquitto_sub -h broker.connectmq.com -p 1883 -i check-k7f3q2 -t 'connectmq/demo/k7f3q2/#' -v ile test edin.
  3. json_extract NULL döndürüyor: anahtar adı veya JSON yolu farklı ya da payload JSON değil. Önce birkaç ham satırı inceleyin.
  4. Veritabanı hızla büyüyor: saklama işini ekleyin ve publisher tarafında yalnızca değişen değerleri saklamayı düşünün.
  5. Logger mesaj düşürüyor: BATCH_SIZE değerini artırın, veritabanını daha hızlı bir depolamaya taşıyın veya aboneliği gerçekten ihtiyaç duyduğunuz topic'lerle sınırlayın.

Prototipten Üretime

Gerçek kurulumlarda logger'ı yeniden başlatmalardan sonra tekrar ayağa kalkacak şekilde bir servis olarak (Linux'ta systemd) çalıştırın ve herkese açık broker yerine özel bir broker'a yönlendirin. ConnectMQ ile portalda bir broker oluşturur, üretilen kullanıcı adı, parola ve client ID'yi kopyalar ve 8883 portunda TLS'i etkinleştirirsiniz; Python MQTT TLS eğitimi tam ayarları gösterir. ConnectMQ broker'ı mesaj geçmişini kendisi saklamaz; bu nedenle bunun gibi harici bir subscriber dürüst ve önerilen desendir. İstemci kodu için bir başlangıç noktasına ihtiyacınız varsa paho mqtt python eğitimine, diğer diller için MQTT istemci kütüphanelerine genel bakış yazısına bakın. Python sqlite3 dokümantasyonu transaction'ları ve bağlantı seçeneklerini derinlemesine ele alır.

ConnectMQ ile Başlayın

ConnectMQ; MQTT 3.1.1 ve 5.0, TLS ve WebSocket sunan, ayrıca cihazlarınızın gönderdiklerini kaydetmeden önce kontrol edebilmeniz için canlı bir topic gezgini içeren barındırılan bir MQTT broker hizmetidir. Bugün broker.connectmq.com üzerinden kayıt almaya başlayın, ardından kendi bulut MQTT broker'ınıza geçin. Serverless planında ücretsiz hesap oluşturun, kredi kartı gerekmez.

Sık Sorulan Sorular

SQLite sürekli MQTT verisini kaldırabilir mi?

Evet, orta düzey hızlarda tek bir logger için. Insert'leri transaction'lar içinde toplu yapmak ve WAL modunu etkinleştirmek, SQLite'ın mütevazı donanımda birçok sensörden gelen sürekli bir akışı karşılamasını sağlar.

Neden doğrudan on_message içinde insert yapmıyoruz?

Callback içinde commit yapmak, paho'nun network loop'unu disk G/Ç'si üzerinde bloklar. Yük altında bu, keepalive ping'lerini ve onayları geciktirir; bu yüzden bir kuyruk ve ayrı bir yazıcı daha güvenilirdir.

Logger yeniden başlarken mesaj kaybetmekten nasıl kaçınırım?

Kalıcı bir MQTT 5 oturumuyla (clean_start=False ve bir session expiry interval) QoS 1 kullanın; böylece broker, oturum süresi sınırı dahilinde siz çevrimdışıyken mesajları kuyruğa alır.

SQLite'ta binary payload saklayabilir miyim?

Evet. payload sütununu BLOB olarak değiştirin ve metne çözmek yerine msg.payload değerini doğrudan ekleyin.

İlgili yazılar

Test, Betik ve Hata Ayıklama için En İyi MQTT CLI Araçları

mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, MQTT.js CLI, curl ve mqttui'nin uygulamalı karşılaştırması; betik yazma ve hata ayıklama için doğru aracı seçebilmeniz için aynı test broker.connectmq.com üzerinde çalıştırıldı.

7 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.