Raspberry Pi

Raspberry Pi MQTT Ekran: Python ile Dokunmatik Kontrol Paneli Yapın

Raspberry Pi üzerinde tam ekran bir dokunmatik kontrol paneli yapın. Butonlar MQTT komutları yayınlar, ekran retained mesajlardan gerçek cihaz durumunu yansıtır ve panel açılışta otomatik olarak başlar.

Raspberry pi mqtt ekran kontrol paneli olarak butonları ve cihaz durumlarını gösteren Raspberry Pi dokunmatik ekranı
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 Raspberry Pi MQTT ekran, cihazlarınızın ne yaptığını gösteren ve tek dokunuşla onları kontrol etmenizi sağlayan dokunmatik bir paneldir. Bu eğitimde Python ile Tkinter ve paho-mqtt kullanarak bir tane yapacaksınız: butonlar komut yayınlar, etiketler cihazların kendi durum topic'lerinden güncellenir ve panel Pi açıldığında tam ekran başlar. Bu desen röleler, lambalar, pompalar veya MQTT konuşan herhangi bir şeyle çalışır.

Komut Topic'leri ve Durum Topic'leri

Kendin yap kontrol panellerindeki en yaygın hata, butonun rengini basıldığı anda değiştirmektir. Bu, gerçekte ne olduğunu değil, ne istediğinizi gösterir. Röle kartı çevrimdışıysa panel yalan söyler.

Sağlam desen, her cihaz için iki topic'i birbirinden ayırır:

Topic Kim yayınlar Retained Amaç
.../<device>/set Panel (veya herhangi bir kontrolcü) Hayır Bir istek: ON, OFF, TOGGLE
.../<device>/state Cihazın kendisi Evet Gerçek: ON veya OFF
.../<device>/status Cihaz, artı last will Evet online veya offline

Panel set topic'ine yayın yapar ve ekranını yalnızca state üzerinde bir mesaj geldiğinde değiştirir. state retained olduğu için panel, abone olduğu anda, hatta yeniden başlatmanın hemen ardından bile her cihazın güncel durumunu öğrenir. Komutlar bilerek retained yapılmaz; aksi halde yeniden bağlanan bir cihaz eski bir komutu tekrar oynatırdı. Retained mesajlar rehberi bu ayrımın neden önemli olduğunu açıklar.

Henüz cihazınız yoksa, MQTT üzerinden Raspberry Pi röle eğitimi bu panelin kontrol ettiği cihaz tarafını oluşturur.

Donanım

  • Raspberry Pi 4 veya 5 ya da basit bir panel için Pi 3B+.
  • Resmi Raspberry Pi Touch Display (7 inç veya Touch Display 2) ya da USB dokunmatikli herhangi bir HDMI ekran.
  • Masaüstlü Raspberry Pi OS (Bookworm). Tkinter, Wayland masaüstünde ek yapılandırma gerektirmeden Xwayland üzerinden çalışır.

Bağımlılıkları Kurun

sudo apt update
sudo apt install -y python3-tk python3-venv
python3 -m venv --system-site-packages ~/panel-env
~/panel-env/bin/pip install "paho-mqtt>=2.0"

--system-site-packages bayrağı, sanal ortamın pip'ten kurulamayan sistem Tkinter paketini kullanmasını sağlar.

Tkinter'ı Kararlı Tutan İş Parçacığı Kuralı

paho-mqtt'nin loop_start() fonksiyonu ağ işlemlerini bir arka plan iş parçacığında çalıştırır ve geri çağırmaları o iş parçacığında tetiklenir. Tkinter iş parçacığı güvenli değildir: widget'lara başka bir iş parçacığından dokunmak, çoğu zaman günler sonra ortaya çıkan rastgele donmalara ve çökmelere yol açar. Çözüm bir kuyruktur. MQTT geri çağırmaları mesajları yalnızca bir queue.Queue içine iter; Tkinter ana döngüsü de kuyruğu her 100 ms'de bir root.after() ile boşaltır. Tüm widget güncellemeleri GUI iş parçacığında gerçekleşir.

Raspberry Pi MQTT Ekran Kodu

Bunu ~/panel.py olarak kaydedin:

#!/usr/bin/env python3
"""Touchscreen MQTT control panel for Raspberry Pi."""
import json
import os
import queue
import ssl
import tkinter as tk

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")          # YOUR_USERNAME
PASSWORD = os.getenv("MQTT_PASSWORD")          # YOUR_PASSWORD
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", "panel-e40b77")  # YOUR_CLIENT_ID
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/e40b77/home")

DEVICES = [
    ("hall_light", "Hall light"),
    ("garden_pump", "Garden pump"),
    ("garage_door", "Garage door"),
    ("heater", "Heater"),
]
SENSOR_TOPIC = f"{PREFIX}/living/climate"

COLORS = {"ON": "#2e7d32", "OFF": "#37474f", "UNKNOWN": "#5d4037", "OFFLINE": "#b71c1c"}

inbox = queue.Queue()


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        inbox.put(("_conn", f"error: {reason_code}"))
        return
    subs = [(f"{PREFIX}/{d}/state", 1) for d, _ in DEVICES]
    subs += [(f"{PREFIX}/{d}/status", 1) for d, _ in DEVICES]
    subs.append((SENSOR_TOPIC, 0))
    client.subscribe(subs)
    inbox.put(("_conn", "connected"))


def on_disconnect(client, userdata, flags, reason_code, properties):
    inbox.put(("_conn", "reconnecting"))


def on_message(client, userdata, msg):
    inbox.put((msg.topic, msg.payload.decode(errors="replace")))


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id=CLIENT_ID, protocol=mqtt.MQTTv5)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
client.reconnect_delay_set(min_delay=1, max_delay=30)
client.connect_async(HOST, PORT, keepalive=30)
client.loop_start()

# ---------- GUI ----------
root = tk.Tk()
root.title("Control Panel")
root.configure(bg="#121212")
root.attributes("-fullscreen", True)
root.bind("<Escape>", lambda e: root.destroy())

header = tk.Frame(root, bg="#121212")
header.pack(fill="x", padx=20, pady=(16, 8))
climate_lbl = tk.Label(header, text="-- °C  --%", font=("DejaVu Sans", 28),
                       fg="white", bg="#121212")
climate_lbl.pack(side="left")
conn_lbl = tk.Label(header, text="connecting", font=("DejaVu Sans", 16),
                    fg="#ffb300", bg="#121212")
conn_lbl.pack(side="right")

grid = tk.Frame(root, bg="#121212")
grid.pack(expand=True, fill="both", padx=20, pady=20)
for c in range(2):
    grid.columnconfigure(c, weight=1)
for r in range((len(DEVICES) + 1) // 2):
    grid.rowconfigure(r, weight=1)

buttons = {}
state = {d: "UNKNOWN" for d, _ in DEVICES}
online = {d: True for d, _ in DEVICES}


def send_toggle(dev):
    client.publish(f"{PREFIX}/{dev}/set", "TOGGLE", qos=1)
    buttons[dev].configure(text=f"{dict(DEVICES)[dev]}\n...")  # pending, not assumed


def render(dev):
    label = dict(DEVICES)[dev]
    if not online[dev]:
        text, color = f"{label}\nOFFLINE", COLORS["OFFLINE"]
    else:
        text, color = f"{label}\n{state[dev]}", COLORS.get(state[dev], COLORS["UNKNOWN"])
    buttons[dev].configure(text=text, bg=color, activebackground=color)


for i, (dev, label) in enumerate(DEVICES):
    b = tk.Button(grid, text=f"{label}\n--", font=("DejaVu Sans", 26, "bold"),
                  fg="white", bg=COLORS["UNKNOWN"], relief="flat", bd=0,
                  highlightthickness=0, command=lambda d=dev: send_toggle(d))
    b.grid(row=i // 2, column=i % 2, sticky="nsew", padx=10, pady=10)
    buttons[dev] = b


def pump_inbox():
    while True:
        try:
            topic, payload = inbox.get_nowait()
        except queue.Empty:
            break
        if topic == "_conn":
            conn_lbl.configure(text=payload,
                               fg="#66bb6a" if payload == "connected" else "#ffb300")
            continue
        if topic == SENSOR_TOPIC:
            try:
                d = json.loads(payload)
                climate_lbl.configure(text=f"{d.get('t', '--')} °C  {d.get('rh', '--')}%")
            except ValueError:
                pass
            continue
        parts = topic.split("/")
        dev, kind = parts[-2], parts[-1]
        if dev not in state:
            continue
        if kind == "state":
            state[dev] = payload.strip().upper()
        elif kind == "status":
            online[dev] = payload.strip().lower() == "online"
        render(dev)
    root.after(100, pump_inbox)


root.after(100, pump_inbox)
try:
    root.mainloop()
finally:
    client.loop_stop()
    client.disconnect()

Basılan bir buton, cihaz yeni durumunu onaylayana kadar ... gösterir. Onay gelmezse buton bekleme işaretini göstermeye devam eder; bu da sorunu gizlemek yerine kullanıcıya bir şeylerin yanlış olduğunu söyler. Durum topic'i offline diyen cihazların butonları kırmızıya döner. Pencere başlık çubuğu olmadan tam ekrandır; test sırasında çıkmak için klavyede Escape'e basın.

İnsanların Gerçekten Kullanabileceği Bir Panel Tasarlamak

Duvardaki bir kontrol paneli, çoğu zaman ıslak veya eldivenli elleriyle aceleyle kullanan insanlar tarafından kullanılır; bu yüzden birkaç düzen kuralı görsel cilalamadan daha önemlidir:

  • Hedefleri büyük yapın. Parmaklar bir fareden çok daha az hassastır. Yukarıdaki ızgara, 7 inçlik bir ekranda her cihaza ekranın dörtte birini verir; bu da yaklaşık olarak doğrudur. Tek bir sayfaya altı veya sekizden fazla cihaz sıkıştırmaktan kaçının.
  • Renk artı metin kullanın. Yeşil ve gri durumu hızlıca iletir, ancak bazı kullanıcılar renkleri ayırt edemez. Buton metni her zaman ayrıca ON, OFF veya OFFLINE yazar.
  • Bekleyen durumları dürüstçe gösterin. Bir dokunuştan sonraki ... işareti, kullanıcıya komutun gönderildiğini ve panelin cihazı beklediğini söyler; bu da insanların tekrar tekrar dokunup bir cihazı iki kez değiştirmesini önler.
  • Bağlantı göstergesini görünür tutun. Panelin kendisi çevrimdışıysa, ekrandaki her durum eskimiş olabilir. Başlık etiketi bunu açıkça gösterir.
  • Riskli cihazlar için açık komutları tercih edin. TOGGLE lambalar için kullanışlıdır. Bir ısıtıcı veya kapı için ayrı ON ve OFF butonları ya da bir onay adımı yanlışlıkla etkinleştirmeyi önler.

Simüle Edilmiş Cihazlarla Test Edin

Herhangi bir donanım olmadan tüm paneli test edebilirsiniz. Başka bir makinede bir terminal açın ve retained bir durum yayınlayarak koridor lambası gibi davranın:

mosquitto_pub -h broker.connectmq.com -p 1883 -i sim-e40b77 -r -q 1 \
  -t connectmq/demo/e40b77/home/hall_light/state -m ON

Buton yeşile döner. Şimdi panelin gönderdiği komutları izleyin:

mosquitto_sub -h broker.connectmq.com -p 1883 -i spy-e40b77 \
  -t 'connectmq/demo/e40b77/home/+/set' -v

Koridor lambası butonuna dokunun; TOGGLE görünür. Gidiş-dönüşü tamamlamak için durum topic'ine OFF yayınlayın. Genel MQTT test broker'ının herkese açık olduğunu unutmayın; topic önekinizi tahmin eden herkes butonlarınıza da basabilir. Bu bir demo için sorun değildir, ancak bir garaj kapısı için kabul edilemez.

Panelin Açılışta Başlamasını Sağlayın

Pi'yi otomatik oturum açmayla masaüstüne açılacak şekilde ayarlayın (sudo raspi-config, System Options, Boot / Auto Login, Desktop Autologin) ve panelin her zaman görünür olmasını istiyorsanız Display Options altında ekran kararmasını devre dışı bırakın. Ardından bir autostart girdisi ekleyin. Güncel Bookworm imajlarının kullandığı labwc masaüstünde:

mkdir -p ~/.config/labwc
echo "/home/pi/panel-env/bin/python /home/pi/panel.py &" >> ~/.config/labwc/autostart

Wayfire çalıştıran eski Bookworm sürümlerinde aynı komutu ~/.config/wayfire.ini dosyasındaki bir [autostart] bölümüne ekleyin. pi yerine kendi kullanıcı adınızı yazın.

Gerçek Cihazlar İçin Bir Raspberry Pi MQTT Ekranının Güvenliği

Bir kontrol paneli fiziksel şeyleri hareket ettiren komutlar yayınlar; bu yüzden kimlik doğrulama ve şifreleme içeren özel bir broker'a ihtiyaç duyar. ConnectMQ gibi yönetilen bir MQTT broker, Pi üzerinde broker yazılımı çalıştırmadan tam olarak bunu sağlar. Portalda bir broker oluşturun, oluşturulan kullanıcı adını, parolayı ve client ID'yi kopyalayın ve paneli şununla başlatın:

export MQTT_PORT=8883
export MQTT_USERNAME=YOUR_USERNAME
export MQTT_PASSWORD=YOUR_PASSWORD
export MQTT_CLIENT_ID=YOUR_CLIENT_ID
export MQTT_PREFIX=home
/home/pi/panel-env/bin/python /home/pi/panel.py

Aynı değişkenleri autostart için küçük bir sarmalayıcı betiğe koyun. ConnectMQ her client ID'yi kendi kimlik bilgilerine bağlar; bu yüzden panelin ve her cihazın ayrı kimlik bilgilerine ihtiyacı vardır. Bu aynı zamanda sızdırılmış bir cihaz parolasının paneli taklit etmek için kullanılamayacağı anlamına gelir. Ücretsiz planda QoS en fazla 1'dir; bu panelin kullandığı da budur.

Cihaz tarafına da güvenlik ekleyin. Bir garaj kapısı kontrolcüsü, komutu kim gönderirse göndersin, kendi sensörleri yolda bir şey olduğunu söylüyorsa TOGGLE komutunu yok saymalıdır. Panel bir kolaylıktır, bir güvenlik sistemi değil.

Sorun Giderme

Belirti Neden Çözüm
Pencere saatler sonra donuyor Widget'lar MQTT iş parçacığından güncelleniyor Widget'lara yalnızca pump_inbox içinde dokunun
Butonlar kahverengi kalıyor (UNKNOWN) Cihaz retained durum yayınlamıyor Durumu retain ile yayınlayın
Dokunuş görünür bir şey yapmıyor Cihaz çevrimdışı veya topic yanlış set topic'ini mosquitto_sub ile izleyin
_tkinter.TclError: no display Masaüstü olmadan SSH'tan başlatıldı Autostart kullanın veya export DISPLAY=:0
Bağlantı sürekli gidip geliyor Aynı client ID iki kez kullanılıyor Her istemcinin benzersiz bir ID'ye ihtiyacı var

ConnectMQ ile Başlayın

Paneliniz gerçek bir şeyi kontrol etmeden önce onu özel bir broker'a taşıyın: app.connectmq.com adresinden kredi kartı gerekmeden ücretsiz Serverless plana kaydolun ve cihaz topic'lerinizi kontrol etmek için portalın tarayıcı içi MQTT istemcisini kullanın. Yalnızca görüntüleme yapan bir ekran için Raspberry Pi kiosk paneline, bu projenin temelleri için de Raspberry Pi MQTT eğitimine göz atın.

Sık Sorulan Sorular

Dokunmatik ekran için neden bir web sayfası yerine Tkinter kullanılıyor?

Tkinter Python ile birlikte gelir, bir saniyede başlar ve çok az bellek kullanır; bu da eski Pi'lere uygundur. Tarayıcı tabanlı bir panel de geçerlidir ve HTML biliyorsanız stil vermesi daha kolaydır.

Birden fazla panel aynı cihazları kontrol edebilir mi?

Evet. Her panel aynı retained durum topic'lerine abone olur; böylece hepsi otomatik olarak senkronize kalır. Her birinin kendi client ID'sine ihtiyacı vardır.

Bir butona basılırken Pi ağ bağlantısını kaybederse ne olur?

Komut cihaza hiç ulaşmayabilir, ancak buton bir durum onayı gelene kadar bekleme işaretini göstermeye devam eder. Kullanıcı hiçbir şey olmadığını görür ve panel bağlı göründüğünde tekrar dokunabilir.

Tkinter, Bookworm'daki Wayland masaüstüyle çalışır mı?

Evet, otomatik olarak Xwayland üzerinden çalışır. Dokunmatik giriş, butonlar için fare tıklamalarıyla aynı şekilde çalışı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.