Raspberry Pi

Raspberry Pi'de systemd ile Python MQTT Script'ini Açılışta Çalıştırma

Bir Python MQTT istemcisini Raspberry Pi'de düzgün bir systemd servisine dönüştürün: ağ ve saat hazır olduktan sonra başlatın, otomatik olarak yeniden başlatın, kimlik bilgilerini bir ortam dosyasında tutun ve temiz bir şekilde kapatın.

Açılışta bir Python istemcisini başlatan systemd servisini gösteren, python mqtt script açılışta çalıştırma yöntemini anlatan Raspberry Pi terminali
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

Bir Python MQTT script'ini açılışta çalıştırmanın güvenilir yolu bir systemd servisidir: istemcinizi ağ hazır olduktan sonra başlatır, çöktüğünde yeniden başlatır, loglarını toplar ve kapanışta temiz bir şekilde durdurur. Bu rehberde bir paho-mqtt script'ini Raspberry Pi üzerinde bir servise dönüştürecek, kimlik bilgilerini kodun dışında saklayacak, TLS'i bozan açılış saati sorununu ele alacak ve günlük işletim komutlarını öğreneceksiniz. Aynı unit dosyası her modern Linux dağıtımında çalışır.

Neden cron veya rc.local Değil de systemd

Bir script'i açılışta başlatmanın birkaç yolu vardır ve bunlar eşit değildir:

Yöntem Çökmede yeniden başlatır Ağı bekler Loglar Temiz kapanış
crontab'da @reboot Hayır Hayır Yalnızca çıktıyı yönlendirirseniz Hayır
/etc/rc.local Hayır Güvenilmez Hayır Hayır
Masaüstü otomatik başlatma Hayır Hayır Hayır Yalnızca masaüstü oturumuyla
systemd servisi Evet, yapılandırılabilir Evet, sıralama ile Yerleşik journald Evet, önce SIGTERM sonra SIGKILL

Bir crontab girdisi daha basit görünür, ancak sonuç gece 3'te sessizce ölen ve bir daha geri gelmeyen bir MQTT gateway'idir. systemd Raspberry Pi OS'ta zaten çalışır; kurulacak bir şey yoktur.

Adım 1: Script'i Hazırlayın

Servis olarak çalışan bir script yapılandırmayı ortam değişkenlerinden okumalı, kendi kendine yeniden bağlanmalı ve systemd SIGTERM gönderdiğinde temiz bir şekilde çıkmalıdır. Bu örnek her 30 saniyede bir çalışma süresiyle birlikte bir heartbeat yayınlar ve abonelerin cihazın ne zaman çevrim dışı olduğunu görebilmesi için bir Last Will kullanır.

#!/usr/bin/env python3
import json
import os
import signal
import threading
import time

import paho.mqtt.client as mqtt

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.environ["MQTT_CLIENT_ID"]            # ayarlanmış ve benzersiz olmalı
PREFIX = os.getenv("TOPIC_PREFIX", "connectmq/demo/s8t4n6/pi1")
INTERVAL = int(os.getenv("PUBLISH_INTERVAL", "30"))

stop = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stop.set())
signal.signal(signal.SIGINT, lambda *_: stop.set())


def on_connect(client, userdata, flags, reason_code, properties):
    print(f"connected to {HOST}:{PORT} ({reason_code})", flush=True)
    client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)


def on_disconnect(client, userdata, flags, reason_code, properties):
    print(f"disconnected ({reason_code}), will retry", flush=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.connect_async(HOST, PORT, keepalive=30)
client.loop_start()

start = time.monotonic()
while not stop.wait(INTERVAL):
    payload = {"uptime_s": int(time.monotonic() - start), "ts": int(time.time())}
    client.publish(f"{PREFIX}/heartbeat", json.dumps(payload), qos=1)

print("stopping", flush=True)
client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(3)
client.disconnect()
client.loop_stop()

Burada connect_async() önemlidir. Düz bir connect(), broker o anda erişilemezse bir istisna fırlatır; bu da açılıştan sonraki ilk saniyelerde yaygındır. connect_async() bağlantıyı ağ döngüsüne devreder; döngü de reconnect_delay_set() ile ayarlanan geri çekilme süreleriyle yeniden dener.

Adım 2: Kalıcı Bir Konuma Kurun

Servisleri ev dizininizdeki Downloads klasöründen çalıştırmayın. Gateway'e kendi dizinini, sanal ortamını ve sistem kullanıcısını verin:

sudo useradd --system --home /opt/mqtt-client --shell /usr/sbin/nologin mqttclient
sudo mkdir -p /opt/mqtt-client
sudo cp heartbeat.py /opt/mqtt-client/
sudo python3 -m venv /opt/mqtt-client/.venv
sudo /opt/mqtt-client/.venv/bin/pip install "paho-mqtt>=2.0"
sudo chown -R mqttclient:mqttclient /opt/mqtt-client

Script GPIO, I2C veya seri port kullanıyorsa kullanıcıyı ilgili gruba ekleyin; örneğin sudo usermod -aG gpio,i2c,dialout mqttclient.

Adım 3: Kimlik Bilgilerini Bir Ortam Dosyasında Tutun

sudo mkdir -p /etc/mqtt-client
sudo tee /etc/mqtt-client/env > /dev/null <<'EOF'
MQTT_HOST=broker.connectmq.com
MQTT_PORT=1883
MQTT_CLIENT_ID=pi1-heartbeat-s8t4n6
TOPIC_PREFIX=connectmq/demo/s8t4n6/pi1
PUBLISH_INTERVAL=30
EOF
sudo chmod 600 /etc/mqtt-client/env

İlk test için bu dosya, kimlik bilgisi gerektirmeyen genel MQTT test broker'ını gösterir. systemd dosyayı süreci başlatmadan önce root olarak okur; bu yüzden dosya yalnızca root tarafından okunabilir kalabilir.

Adım 4: Python MQTT Script'ini Açılışta Çalıştıracak Unit Dosyasını Yazın

/etc/systemd/system/mqtt-client.service dosyasını oluşturun:

[Unit]
Description=Python MQTT client
Wants=network-online.target time-sync.target
After=network-online.target time-sync.target
StartLimitIntervalSec=0

[Service]
Type=simple
User=mqttclient
Group=mqttclient
WorkingDirectory=/opt/mqtt-client
EnvironmentFile=/etc/mqtt-client/env
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/mqtt-client/.venv/bin/python /opt/mqtt-client/heartbeat.py
Restart=always
RestartSec=5
TimeoutStopSec=15
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

Önemli satırların her biri ne yapar:

  1. Wants/After=network-online.target, başlatmayı yalnızca ağ servisi başlayana kadar değil, ağ yapılandırılana kadar geciktirir.
  2. time-sync.target saati bekler (aşağıdaki TLS notuna bakın).
  3. StartLimitIntervalSec=0 başlatma hız sınırını devre dışı bırakır; böylece systemd birkaç hızlı hatadan sonra pes etmek yerine sonsuza kadar denemeye devam eder.
  4. Restart=always ve RestartSec=5, çökmeler ve temiz çıkışlar dahil her çıkıştan beş saniye sonra script'i yeniden başlatır.
  5. PYTHONUNBUFFERED=1, print() çıktısının journal'da hemen görünmesini sağlar.
  6. Protect* seçenekleri süreci bir sandbox'a alır; güvenli Raspberry Pi IoT kontrol listesi bunları daha ayrıntılı açıklar.

Etkinleştirin ve başlatın:

sudo systemctl daemon-reload
sudo systemctl enable --now mqtt-client.service
systemctl status mqtt-client.service

Adım 5: Doğrulayın ve İşletin

Logları canlı izleyin, ardından mesajların geldiğini doğrulamak için başka bir makineden abone olun:

journalctl -u mqtt-client.service -f
mosquitto_sub -h broker.connectmq.com -p 1883 -i boot-viewer-s8t4n6 -t "connectmq/demo/s8t4n6/pi1/#" -v

Şimdi Pi'yi sudo reboot ile yeniden başlatın. Abone önce offline (temiz kapanıştan), Pi geri geldiğinde de online ve heartbeat'leri görür. Çökme sonrası kurtarmayı test etmek için süreci sudo systemctl kill -s SIGKILL mqtt-client.service ile öldürün ve systemd'nin beş saniye sonra onu yeniden başlatmasını izleyin.

Günlük kullanacağınız komutlar:

Görev Komut
Kod veya ortam dosyası değişikliğinden sonra yeniden başlatma sudo systemctl restart mqtt-client
Geçici olarak durdurma sudo systemctl stop mqtt-client
Açılışta devre dışı bırakma sudo systemctl disable mqtt-client
Son açılıştan beri loglar journalctl -u mqtt-client -b
Etkin unit'i gösterme systemctl cat mqtt-client

TLS ve Saat Tuzağı

Çoğu Raspberry Pi modelinde pille desteklenen bir gerçek zamanlı saat (RTC) yoktur (Pi 5'te bir RTC vardır, ancak güç yokken zamanı korumak için pile ihtiyaç duyar). Açılışta saat, NTP senkronize olana kadar son kaydedilen zamandan başlar. Saat çok farklıysa TLS sertifika doğrulaması başarısız olur, çünkü sertifika henüz geçerli değil veya süresi dolmuş görünür; script'iniz de 8883 portunda tekrarlanan bağlantı hataları loglar.

time-sync.target sıralaması yalnızca bir servis gerçekten senkronizasyonu bekliyorsa işe yarar. Bunu yapan servisi etkinleştirin:

sudo systemctl enable systemd-time-wait-sync.service
timedatectl status

Bu etkinleştirildiğinde MQTT istemcisi ancak saat senkronize olduktan sonra başlar. Script ayrıca kendi kendine yeniden bağlandığı için bu olmadan da toparlanır, sadece daha yavaş.

Ayakta Kalmayan Bir Servisin Sorunlarını Gidermek

Bir servis hatalı davrandığında systemctl status ve journal neredeyse her zaman cevabı içerir. Python MQTT istemcilerinde en sık görülen hatalar şunlardır:

  1. status=203/EXEC: systemd ExecStart komutunu çalıştıramıyor. venv Python yolu yanlış veya venv farklı bir kullanıcı tarafından oluşturulmuş ve okunamıyor. Mutlak yollar kullanın ve bunları ls -l ile kontrol edin.
  2. ModuleNotFoundError: paho: unit venv yerine sistem Python'unu çalıştırıyor. ExecStart komutunu python3 yerine /opt/mqtt-client/.venv/bin/python yoluna yönlendirin.
  3. KeyError: 'MQTT_CLIENT_ID': ortam dosyasında bir değişken eksik veya yazım hatası var. EnvironmentFile satırlarının export olmadan düz KEY=value biçiminde olduğunu unutmayın.
  4. Bir dosya veya cihazda PermissionError: ProtectSystem=strict yolu salt okunur yapmış veya servis kullanıcısının bir grubu eksik. Dizini ReadWritePaths= satırına veya kullanıcıyı doğru gruba ekleyin.
  5. Tekrarlanan "connection refused" veya "not authorised": yanlış port, kimlik bilgileri veya yinelenen bir client ID. Aynı client ID'ye sahip iki süreç birbirinin bağlantısını sürekli koparır; bu yüzden elle yaptığınız bir test çalıştırmasının hâlâ açık olmadığından emin olun.
  6. Sonsuza kadar birkaç saniyede bir yeniden başlama: script hemen çıkıyor. Hatayı terminalinizde görmek için tam ExecStart komutunu servis kullanıcısı olarak sudo -u mqttclient ile elle çalıştırın.

Özel Bir Broker'a Geçmek

Servis çalıştığında üretim kimlik bilgilerine geçin. ConnectMQ üzerinde bir broker oluşturun, ardından /etc/mqtt-client/env dosyasını düzenleyin:

MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
TOPIC_PREFIX=site1/gateways/pi1

Servisi yeniden başlatın; script TLS üzerinden bağlanır. Bir yönetilen MQTT broker sunucu tarafını, systemd de istemci tarafını çalışır durumda tutar; ikisi birlikte kesintilerin çoğunu karşılar. Çıkış yerine takılma gibi görünen çökmeler için Raspberry Pi güvenilirlik ipuçlarında anlatıldığı gibi bir uygulama watchdog'u ekleyin ve ağ kesintileri sırasında verileri Python'da çevrim dışı MQTT tamponlama ile tamponlayın. systemd.service kılavuzu tüm seçenekleri listeler.

Pi'de MQTT'ye yeni misiniz? Raspberry Pi MQTT rehberiyle başlayın, ardından istemcinizi kalıcı hale getirmek için buraya geri dönün.

Pi'nizi ConnectMQ ile Bağlı Tutun

Kredi kartı gerekmeden ücretsiz Serverless planı için app.connectmq.com adresinden kaydolun ve oluşturulan kimlik bilgilerini ortam dosyanıza yapıştırın. ConnectMQ portalı cihazlarınızı listeler; böylece bir sonraki yeniden başlatmadan sonra Pi'ye giriş yapmadan servisinizin yeniden çevrim içi olduğunu doğrulayabilirsiniz.

Sık Sorulan Sorular

Servisim neden ağ hazır olmadan başlıyor?

After=network.target çalışan bir bağlantıyı değil, yalnızca ağ servisini bekler. Wants= ve After=network-online.target kullanın ve script'in connect_async() ile kendi kendine yeniden bağlanmasına izin verin.

journalctl'de neden hiç çıktı görmüyorum?

Python, stdout bir terminal olmadığında çıktıyı tamponlar. PYTHONUNBUFFERED=1 ayarlayın, Python'u -u ile çalıştırın veya print() fonksiyonuna flush=True verin.

Sistem servisi yerine kullanıcı servisi mi kullanmalıyım?

Kullanıcı servisleri, lingering etkinleştirilmedikçe yalnızca o kullanıcının oturumu açıkken çalışır. Özel bir User= ile bir sistem servisi daha basittir ve herhangi bir giriş yapmadan açılışta başlar.

Birden fazla MQTT script'ini nasıl çalıştırırım?

Her script için bir unit dosyası oluşturun veya her örnek için ayrı bir ortam dosyasıyla mqtt-client@.service gibi bir şablon unit kullanın. Her örneğin kendine ait benzersiz bir client ID'si olmalıdır.

İlgili yazılar

RC522 Okuyucu ile Raspberry Pi RFID MQTT Erişim Kaydı

Bir RC522 RFID okuyucuyu Raspberry Pi'ye bağlayın, etiketleri Python ile okuyun, yerel bir izin listesiyle karşılaştırın ve her erişim denemesini kayıt araçlarının, panellerin ve uyarı araçlarının tüketebileceği bir MQTT olayı olarak yayınlayın.

6 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.