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:
Wants/After=network-online.target, başlatmayı yalnızca ağ servisi başlayana kadar değil, ağ yapılandırılana kadar geciktirir.time-sync.targetsaati bekler (aşağıdaki TLS notuna bakın).StartLimitIntervalSec=0baş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.Restart=alwaysveRestartSec=5, çökmeler ve temiz çıkışlar dahil her çıkıştan beş saniye sonra script'i yeniden başlatır.PYTHONUNBUFFERED=1,print()çıktısının journal'da hemen görünmesini sağlar.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:
status=203/EXEC: systemdExecStartkomutunu ç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 -lile kontrol edin.ModuleNotFoundError: paho: unit venv yerine sistem Python'unu çalıştırıyor.ExecStartkomutunupython3yerine/opt/mqtt-client/.venv/bin/pythonyoluna yönlendirin.KeyError: 'MQTT_CLIENT_ID': ortam dosyasında bir değişken eksik veya yazım hatası var.EnvironmentFilesatırlarınınexportolmadan düzKEY=valuebiçiminde olduğunu unutmayın.- Bir dosya veya cihazda
PermissionError:ProtectSystem=strictyolu salt okunur yapmış veya servis kullanıcısının bir grubu eksik. DiziniReadWritePaths=satırına veya kullanıcıyı doğru gruba ekleyin. - 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.
- Sonsuza kadar birkaç saniyede bir yeniden başlama: script hemen çıkıyor. Hatayı terminalinizde görmek için tam
ExecStartkomutunu servis kullanıcısı olaraksudo -u mqttclientile 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.



