IoT ve Yapay Zekâ

Doğal Dil ile Cihaz Kontrolü: Sade İstekleri Doğrulanmış MQTT Komutlarına Dönüştürün

Mutfak ışıklarını yüzde 30 kısmak gibi istekleri function calling destekli bir LLM, bir cihaz kaydı ve katı bir doğrulama katmanıyla doğrulanmış MQTT komutlarına dönüştüren uygulamalı bir eğitim.

Yazılı bir isteğin ışıklar ve termostat için doğrulanmış bir MQTT komutuna dönüştüğü doğal dil ile cihaz kontrolü akışı
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

Doğal dil ile cihaz kontrolü, insanların dashboard'lar arasında arama yapmak yerine "ofisi biraz ısıt" veya "oturma odasındaki her şeyi kapat" gibi ne istediklerini söylemelerini sağlar. Bu eğitimde, bir LLM'in istekleri yapılandırılmış fonksiyon çağrılarına çevirdiği, bir doğrulama katmanının her çağrıyı bir cihaz kaydına göre kontrol ettiği ve yalnızca geçerli komutların MQTT üzerinden, komutları onaylayan cihazlara yayınlandığı bir Python servisi oluşturacaksınız.

Akış Nasıl Çalışır?

Temel fikir şu: dil modeli asla MQTT mesajı yazmaz. Yalnızca sizin tanımladığınız bir fonksiyon menüsünden seçim yapar; bu seçimleri kontrol ettikten sonra komutlara dönüştüren ise sizin kodunuzdur.

  1. Kullanıcı isteği. Bir sohbet kutusundan, bir sesli asistan dökümünden veya bir mesajlaşma botundan gelen serbest metin.
  2. Bağlam. Servis, modele cihazların, kullanıcı dostu adlarının ve yeteneklerinin kompakt bir listesini verir.
  3. Function calling. Model sıfır veya daha fazla araç çağrısı döndürür, örneğin set_light(device="kitchen-ceiling", on=true, brightness=30).
  4. Doğrulama. Kod, cihazın var olduğunu, yeteneği desteklediğini ve değerlerin aralık içinde olduğunu kontrol eder.
  5. Yayın. Geçerli komutlar benzersiz bir komut kimliğiyle MQTT üzerinden .../<device>/set topic'ine gider.
  6. Onay. Cihaz değişikliği uygular ve yeni durumunu yayınlar; servis bunu kullanıcıya bildirir.
Katman Güvenilir mi? Görevi
LLM Hayır Niyeti yorumlamak, fonksiyonları ve argümanları seçmek
Doğrulayıcı Evet Kaydı, yetenekleri ve değer aralıklarını uygulamak
MQTT broker Evet Komutları ve durumu güvenilir şekilde iletmek
Cihaz Evet Değişikliği kendi limitleri içinde uygulamak ve durumu bildirmek

Ön Koşullar

  • Python 3.10 veya üzeri.
  • qwen2.5:7b veya llama3.1:8b gibi araç çağrısını destekleyen bir modelle yerel olarak çalışan Ollama. Function calling destekleyen her sağlayıcı aynı şekilde çalışır; yalnızca API çağrısı değişir.
  • Bir MQTT broker. Demo için broker.connectmq.com kullanıyoruz.
ollama pull qwen2.5:7b
pip install ollama "paho-mqtt>=2.0"

Adım 1: Cihaz Kaydını Tanımlayın

Kayıt, neyin var olduğu ve neler yapabildiği konusunda tek doğruluk kaynağıdır. Model bunun bir özetini görür; doğrulayıcı ise tamamını kullanır.

# registry.py
DEVICES = {
    "kitchen-ceiling": {"name": "kitchen ceiling light", "room": "kitchen", "type": "light", "dimmable": True},
    "kitchen-under":   {"name": "kitchen under-cabinet light", "room": "kitchen", "type": "light", "dimmable": False},
    "living-lamp":     {"name": "living room floor lamp", "room": "living room", "type": "light", "dimmable": True},
    "living-blinds":   {"name": "living room blinds", "room": "living room", "type": "blinds"},
    "office-thermo":   {"name": "office thermostat", "room": "office", "type": "thermostat", "min_c": 16, "max_c": 26},
}


def describe_devices():
    lines = []
    for dev_id, d in DEVICES.items():
        extra = ""
        if d["type"] == "light":
            extra = " (dimmable)" if d["dimmable"] else " (on/off only)"
        if d["type"] == "thermostat":
            extra = f" (range {d['min_c']}-{d['max_c']} C)"
        lines.append(f"- {dev_id}: {d['name']}, room={d['room']}, type={d['type']}{extra}")
    return "\n".join(lines)

Adım 2: Fonksiyonları Tanımlayın

Her fonksiyon bir yeteneğe karşılık gelir. Şemada cihaz kimliklerini enum olarak listelemek, modelin geçerli hedefleri seçmesine yardımcı olur.

# tools.py
from registry import DEVICES

LIGHTS = [k for k, d in DEVICES.items() if d["type"] == "light"]
BLINDS = [k for k, d in DEVICES.items() if d["type"] == "blinds"]
THERMOS = [k for k, d in DEVICES.items() if d["type"] == "thermostat"]

TOOLS = [
    {"type": "function", "function": {
        "name": "set_light",
        "description": "Turn a light on or off and optionally set brightness in percent (dimmable lights only).",
        "parameters": {"type": "object", "properties": {
            "device": {"type": "string", "enum": LIGHTS},
            "on": {"type": "boolean"},
            "brightness": {"type": "integer", "minimum": 1, "maximum": 100}},
            "required": ["device", "on"]}}},
    {"type": "function", "function": {
        "name": "set_blinds",
        "description": "Set blinds position in percent, 0 is closed and 100 is fully open.",
        "parameters": {"type": "object", "properties": {
            "device": {"type": "string", "enum": BLINDS},
            "position": {"type": "integer", "minimum": 0, "maximum": 100}},
            "required": ["device", "position"]}}},
    {"type": "function", "function": {
        "name": "set_thermostat",
        "description": "Set the target temperature in degrees Celsius.",
        "parameters": {"type": "object", "properties": {
            "device": {"type": "string", "enum": THERMOS},
            "target_c": {"type": "number"}},
            "required": ["device", "target_c"]}}},
]

Adım 3: Her Çağrıyı Doğrulayın

Şemalar modele yol gösterir ama onu bağlamaz. Özellikle küçük yerel modeller bazen sayıları metin olarak döndürür, cihaz kimlikleri uydurur veya aralıkları görmezden gelir. Doğrulayıcı, normalleştirilmesi güvenli olanı normalleştirir ve geri kalan her şeyi bir gerekçeyle reddeder.

# validate.py
from registry import DEVICES


class Invalid(Exception):
    pass


def as_int(value, field):
    try:
        return int(round(float(value)))
    except (TypeError, ValueError):
        raise Invalid(f"{field} must be a number")


def validate(name, args):
    dev_id = args.get("device")
    dev = DEVICES.get(dev_id)
    if dev is None:
        raise Invalid(f"unknown device {dev_id!r}")
    if name == "set_light":
        if dev["type"] != "light":
            raise Invalid(f"{dev_id} is not a light")
        on = args.get("on")
        if isinstance(on, str):
            on = on.lower() in ("true", "on", "1")
        cmd = {"on": bool(on)}
        if "brightness" in args and args["brightness"] is not None:
            if not dev["dimmable"]:
                raise Invalid(f"{dev['name']} is not dimmable")
            b = as_int(args["brightness"], "brightness")
            if not 1 <= b <= 100:
                raise Invalid("brightness must be 1-100")
            cmd["brightness"] = b
        return dev_id, cmd
    if name == "set_blinds":
        if dev["type"] != "blinds":
            raise Invalid(f"{dev_id} is not blinds")
        p = as_int(args.get("position"), "position")
        if not 0 <= p <= 100:
            raise Invalid("position must be 0-100")
        return dev_id, {"position": p}
    if name == "set_thermostat":
        if dev["type"] != "thermostat":
            raise Invalid(f"{dev_id} is not a thermostat")
        try:
            t = round(float(args.get("target_c")), 1)
        except (TypeError, ValueError):
            raise Invalid("target_c must be a number")
        if not dev["min_c"] <= t <= dev["max_c"]:
            raise Invalid(f"target must be {dev['min_c']}-{dev['max_c']} C")
        return dev_id, {"target_c": t}
    raise Invalid(f"unknown function {name}")

Adım 4: Doğal Dil ile Cihaz Kontrolü Servisi

Şimdi her şeyi birbirine bağlayın. Servis bir isteği okur, modelden araç çağrıları ister, bunları doğrular ve komutları yayınlar.

# nl_control.py
import json
import time
import uuid

import ollama
import paho.mqtt.client as mqtt

from registry import describe_devices
from tools import TOOLS
from validate import Invalid, validate

MODEL = "qwen2.5:7b"
PREFIX = "connectmq/demo/e3b90c/home"   # use your own random id

SYSTEM = (
    "You control smart home devices by calling functions. "
    "Only use device IDs from this list:\n" + describe_devices() + "\n"
    "If a request is ambiguous or no device matches, ask a short clarifying question instead of calling a function. "
    "Never guess temperatures outside the allowed range."
)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="nl-control-e3b90c")
client.on_message = lambda c, u, m: print(f"  state {m.topic.split('/')[-2]}: {m.payload.decode()}")
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.subscribe(f"{PREFIX}/+/state", qos=1)
client.loop_start()


def handle(request):
    resp = ollama.chat(
        model=MODEL,
        messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": request}],
        tools=TOOLS,
        options={"temperature": 0},
    )
    calls = resp.message.tool_calls or []
    if not calls:
        print("assistant:", resp.message.content)
        return
    for call in calls:
        name, args = call.function.name, dict(call.function.arguments)
        try:
            dev_id, cmd = validate(name, args)
        except Invalid as err:
            print(f"rejected {name}({args}): {err}")
            continue
        cmd.update({"id": str(uuid.uuid4()), "ts": int(time.time() * 1000), "source": "nl"})
        client.publish(f"{PREFIX}/{dev_id}/set", json.dumps(cmd), qos=1)
        print(f"sent {dev_id}: {cmd}")


if __name__ == "__main__":
    while True:
        text = input("\nyou: ").strip()
        if text:
            handle(text)

Temperature değerini 0'a ayarlamak araç seçimini daha tekrarlanabilir kılar; kontrol görevlerinde istediğiniz de budur.

Adım 5: Simüle Edilmiş Bir Cihaz Ekleyin ve Test Edin

Küçük bir simülatör tüm set topic'lerine abone olur, komutları uygular ve retained durum yayınlar:

# sim_home.py
import json
import paho.mqtt.client as mqtt

PREFIX = "connectmq/demo/e3b90c/home"
state = {}


def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(f"{PREFIX}/+/set", qos=1)


def on_message(client, userdata, msg):
    dev = msg.topic.split("/")[-2]
    cmd = json.loads(msg.payload)
    state.setdefault(dev, {}).update({k: v for k, v in cmd.items() if k not in ("id", "ts", "source")})
    client.publish(f"{PREFIX}/{dev}/state", json.dumps({**state[dev], "lastCommand": cmd["id"]}), qos=1, retain=True)


c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="sim-home-e3b90c")
c.on_connect = on_connect
c.on_message = on_message
c.connect("broker.connectmq.com", 1883, keepalive=60)
c.loop_forever()

Önce simülatörü, ardından servisi çalıştırın ve şu istekleri deneyin:

İstek Beklenen sonuç
Mutfak tavan ışığını yüzde 30'a kıs Parlaklığı 30 olan tek bir set_light çağrısı
Oturma odasındaki her şeyi kapat Lamba için set_light ve yoruma bağlı olarak bir soru ya da set_blinds
Ofisi 35 derece yap Doğrulayıcı tarafından reddedilir veya modelden netleştirici bir yanıt gelir
Dolap altı ışığını kıs Reddedilir: kısılabilir değil
Garaj ışığını aç Netleştirici soru, çünkü böyle bir cihaz yok

İlginç olan, başarısız durumlardır. Her biri ya modelin netleştirme istemesiyle ya da doğrulayıcı tarafından yakalanır ve geçersiz hiçbir şey broker'a ulaşmaz.

Model Neden Asla MQTT Payload'ı Yazmaz?

Fonksiyon katmanını atlayıp modelden doğrudan bir topic ve JSON payload üretmesini istemek cazip gelebilir. Bu yaklaşım ince şekillerde bozulur. Model doğru görünen ama var olmayan bir topic uydurabilir, cihaz türleri arasında anahtar adlarını karıştırabilir veya yanlış birimlerle geçerli bir JSON üretebilir. Bu hataların her biri broker'a ulaşır ve bazı cihazlar hatalı komutları sessizce görmezden gelerek kullanıcıyı değişikliğin gerçekleştiğine inandırır.

Function calling ile model, parametrelerini sizin tanımladığınız birkaç adlandırılmış işlem arasından seçim yapar. İşlemden topic'e ve payload biçimine eşlemenin sahibi sizin kodunuzdur; bu sayede bir cihazın firmware'ini veya topic düzenini değiştirmek asla prompt'ları değiştirmeyi gerektirmez. Doğrulayıcı da komutların oluşturulduğu tek yer hâline gelir; bu da onu hiç model kullanmadan sıradan birim testleriyle test etmeyi kolaylaştırır.

Takip İsteklerini Ele Almak

Gerçek konuşmalar nadiren tek seferliktir. İnsanlar "biraz daha parlak" veya "diğerini de" der. Bunu desteklemek için son birkaç kullanıcı ve asistan mesajını messages listesinde tutun ve yakın zamanda değiştirilen cihazların retained durum topic'lerinden okunan mevcut durumunu sistem prompt'una ekleyin. Böylece model "biraz daha parlak" ifadesini az önce değiştirdiği ışıkla eşleştirebilir ve mevcut değerden yeni bir parlaklık hesaplayabilir; doğrulayıcı ise sonucu yine kontrol eder. Eski bağlamın yeni isteklere sızmaması için geçmişi birkaç turla sınırlayın.

Üretime Hazır Hâle Getirmek

Demodan gerçek bir eve, binaya veya tesise geçerken şu noktaları aklınızda tutun:

  1. Özel kimlik bilgileri ve TLS kullanın. Herkese açık MQTT test broker'ı herkes tarafından okunabilir. Gerçek cihazlar için ConnectMQ portalında oluşturulan kimlik bilgileriyle 8883 portundan bağlanın.
  2. Yıkıcı veya olağandışı işlemleri teyit edin. Tüm ışıkları kapatmak sorun değildir; bir kapıyı açmak veya bir alarmı devre dışı bırakmak açık bir teyit gerektirmelidir.
  3. Yalnızca yayınlamakla kalmayın, durumu bekleyin. Başarıyı ancak cihaz durumu komut kimliğini doğruladıktan sonra bildirin.
  4. Kaydı elle yazmak yerine otomatik oluşturun. Yeni cihazların otomatik olarak görünmesi için kaydı retained cihaz duyuru topic'lerinden oluşturun.
  5. Her şeyi kaydedin. Sorun giderme için isteği, araç çağrılarını, doğrulama sonuçlarını ve onayları saklayın.

Aynı doğrulama disiplini endüstriyel ekipmanlara da ölçeklenir; IoT için LLM function calling ve IoT cihazları için LLM ajanları rehberlerimiz bunu şema kütüphaneleri ve insan onayıyla genişletiyor. Tüm yığını çevrimdışı çalıştırmak istiyorsanız bir gateway üzerinde Ollama'yı MQTT ile çalıştırma yazımıza bakın. Dil arayüzlerinin diğer yapay zeka teknikleri arasındaki yerini görmek için yapay zeka ve IoT rehberimizi okuyun.

Kontrol kanalını barındırılan bir MQTT broker üzerinde çalıştırmak; cihazlarınızın, dil servisinin ve dashboard'ların evde veya tesiste port açmadan TCP, TLS veya WebSocket üzerinden tek bir güvenli uç noktaya ulaşması anlamına gelir.

ConnectMQ ile Başlayın

ConnectMQ, prompt'ları test ederken komutları ve durum değişikliklerini canlı izleyebilmeniz için tarayıcı içi MQTT client'ı olan tam yönetilen bir broker sunar. Kredi kartı gerektirmeden ücretsiz Serverless broker'ınızı oluşturun ve insanların cihazları kendi sözcükleriyle kontrol etmesini sağlayın.

Sık Sorulan Sorular

Doğal dil ile cihaz kontrolü için büyük bir bulut modeline ihtiyacım var mı?

Hayır. Araç çağrısı destekleyen küçük yerel modeller, cihaz listesi kısa ve fonksiyonlar net olduğunda basit cihaz komutlarını iyi işler. Yanlış yaptıkları durumları koddaki doğrulama karşılar.

"Işığı aç" gibi belirsiz istekleri nasıl ele alırım?

Sistem prompt'unda modele, birden fazla cihaz eşleştiğinde netleştirici bir soru sormasını söyleyin. Servisiniz soruyu kullanıcıya gösterir ve yanıtı bir sonraki turda geri gönderir.

Metin yerine ses kullanabilir miyim?

Evet. Önce konuşmayı metne dönüştürün ve dökümü aynı akışa verin. Döküm hataları ek bir hata kaynağı oluşturduğundan, riskli işlemler için teyitleri koruyun.

Komutları neden bir set topic'ine yayınlayıp ayrı bir state topic'ini okuyoruz?

Komutları durumdan ayırmak, state topic'inin her zaman cihazın gerçekte ne yaptığını yansıtması anlamına gelir; retained durum da her client'a mevcut değeri anında verir.

İlgili yazılar

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.