Raspberry Pi

Raspberry Pi ile CAN Bus'tan MQTT'ye: Frame'leri Okuyun, Çözün ve Yayınlayın

CAN HAT takılı bir Raspberry Pi'ı, frame'leri python-can ile okuyan, sinyalleri bir DBC dosyasından çözen ve hız sınırlamalı JSON'u bir MQTT broker'a yayınlayan bir gateway'e dönüştürün.

Bir araç veri yoluna bağlı CAN HAT'li Raspberry Pi, Raspberry Pi CAN bus MQTT gateway'ini gösteriyor
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

Raspberry Pi tabanlı bir CAN bus MQTT gateway'i, normalde bir makinenin, aracın veya batarya paketinin içinde kilitli kalan sinyalleri alıp her yerdeki panolar, uyarılar ve analizler için kullanılabilir hale getirir. Bu eğitimde MCP2515 tabanlı bir CAN HAT bağlayacak, can0 arayüzünü ayağa kaldıracak, frame'leri python-can ile okuyacak, bir DBC dosyasıyla çözecek ve temiz JSON'u bir MQTT broker'a yayınlayacaksınız. Örnekler önce broker.connectmq.com adresindeki ücretsiz test broker'ını kullanır, ardından üretim için özel kimlik bilgilerine geçer.

CAN'den MQTT'ye Gateway Nasıl Çalışır?

CAN, her düğümün bir arbitrasyon ID'siyle tanımlanan kısa frame'ler (klasik CAN için en fazla 8 veri baytı) yayınladığı çok yöneticili (multi-master) bir veri yoludur. "Topic" ya da "okuma" kavramı yoktur: 0x1F0 gibi tek bir frame, motor devrini, soğutma suyu sıcaklığını ve bir durum bitini payload'unun belirli bitlerine sıkıştırabilir. Anlam ayrı bir tanımda yaşar; bu genellikle ekipman üreticisinin sağladığı ya da sizin tersine mühendislikle çıkardığınız bir DBC dosyasıdır.

MQTT bunun tam tersidir: adlandırılmış topic'ler, kendini tanımlayan payload'lar ve gelip giden tüketiciler. Gateway'in işi bu iki dünya arasında çeviri yapmaktır:

  1. Linux SocketCAN üzerinden veri yolundan ham frame'leri almak.
  2. İlgilendiğiniz frame'leri adlandırılmış fiziksel değerlere (rpm, derece, volt) çözmek.
  3. Veri hızını düşürmek, çünkü bir CAN veri yolu saniyede binlerce frame taşıyabilir.
  4. Değerleri bir MQTT broker üzerinde iyi yapılandırılmış topic'lere yayınlamak.

İlk denemelerin çoğu 3. adımda hata yapar. Her frame'i ayrı bir MQTT mesajı olarak iletmek ağı ve broker'ı boğar; aşağı akıştaki hiç kimsenin de saniyede 100 kez motor devrine ihtiyacı yoktur. Bir gateway her sinyalin son değerini tutmalı ve anlık görüntüleri sabit bir aralıkla ya da anlamlı bir değişimde yayınlamalıdır.

Gereken Donanım

Öğe Notlar
Raspberry Pi 3, 4 veya 5 40 pinli header ve SPI'a sahip her model çalışır
MCP2515 CAN HAT Waveshare RS485 CAN HAT, PiCAN2 veya benzeri; kart üzerinde yazan kristal frekansını kontrol edin
CAN transceiver Genellikle HAT üzerindedir (SN65HVD230 veya MCP2551)
120 ohm sonlandırma Veri yolunun her iki fiziksel ucunda gereklidir; birçok HAT'te bir jumper vardır
Veri yoluna erişim OBD-II konnektörü, makine CAN portu veya test için ikinci bir düğüm

CAN_H'yi CAN_H'ye, CAN_L'yi CAN_L'ye bağlayın; HAT sağlıyorsa ortak bir toprak da ekleyin. Pi'nız veri yolunun ucundaysa sonlandırma direncini etkinleştirin. Zaten sonlandırılmış bir veri yolunun ortasından bağlanıyorsa sonlandırmayı kapalı bırakın.

can0 Arayüzünü Ayağa Kaldırın

SPI'ı ve MCP2515 overlay'ini etkinleştirin. Raspberry Pi OS Bookworm'da dosya /boot/firmware/config.txt konumundadır; eski sürümler /boot/config.txt kullanır.

dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25

oscillator değeri HAT'inizdeki kristalle eşleşmelidir (birçok Waveshare kartında 12 MHz, PiCAN2 ve diğerlerinde 16 MHz); interrupt ise HAT'in MCP2515 kesme hattı için kullandığı GPIO ile eşleşmelidir. Yanlış oscillator değeri, ayağa kalkan ama hiçbir şey almayan bir arayüzün en yaygın nedenidir. Yeniden başlatın, ardından arayüzü veri yolu bit hızında açın:

sudo reboot
# after reboot
dmesg | grep -i mcp251
sudo apt install -y can-utils
sudo ip link set can0 up type can bitrate 500000
ip -details link show can0
candump can0

500 kbit/s araçlarda ve birçok makinede yaygındır, ancak 250 kbit/s (J1939 kamyonlar ve tarım ekipmanları) ve 125 kbit/s de görülür. candump hiçbir şey göstermiyorsa ve ip -details -statistics link show can0 hatalar veya BUS-OFF durumu bildiriyorsa bit hızı veya kablolama yanlıştır.

Arayüzün açılışta otomatik olarak ayağa kalkması için yönetimini systemd-networkd'ye bırakın:

sudo tee /etc/systemd/network/80-can.network > /dev/null <<'EOF'
[Match]
Name=can0

[CAN]
BitRate=500K
EOF
sudo systemctl enable --now systemd-networkd

python-can, cantools ve paho-mqtt Kurulumu

python3 -m venv ~/can-gw/.venv
source ~/can-gw/.venv/bin/activate
pip install python-can cantools "paho-mqtt>=2.0"

python-can SocketCAN ile konuşur, cantools DBC dosyalarını ayrıştırıp frame'leri çözer, paho-mqtt de broker bağlantısını yönetir. Henüz bir DBC dosyanız yoksa aşağıdaki betik yine çalışır ve beyaz listeye aldığınız ID'ler için ham frame'leri hex olarak yayınlar; bu, sinyalleri tersine mühendislikle çözerken işe yarar.

Raspberry Pi CAN Bus MQTT Betiği

Gateway frame'leri bir döngüde okur, bunları son değerlerden oluşan bir sözlüğe çözer ve saniyede bir anlık görüntü yayınlar. Ayrıca bir Last Will ile retained bir online/offline durumu yayınlar; böylece panolar gateway'in ne zaman kaybolduğunu anlayabilir.

import json
import os
import time
import uuid

import can
import cantools
import paho.mqtt.client as mqtt

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")          # özel bir broker için ayarlayın
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"can-gw-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("TOPIC_PREFIX", "connectmq/demo/k7x2q9/vehicle1")
DBC_PATH = os.getenv("DBC_PATH", "vehicle.dbc")
RAW_IDS = {0x1F0, 0x2A0}                        # DBC'de yoksa hex olarak iletilir
PUBLISH_INTERVAL = 1.0

db = cantools.database.load_file(DBC_PATH) if os.path.exists(DBC_PATH) else None
known_ids = {m.frame_id for m in db.messages} if db else set()

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)


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


client.on_connect = on_connect
client.connect_async(BROKER, PORT, keepalive=30)
client.loop_start()

latest = {}
raw = {}
bus = can.Bus(interface="socketcan", channel="can0")
next_publish = time.monotonic() + PUBLISH_INTERVAL

try:
    while True:
        msg = bus.recv(timeout=0.2)
        if msg is not None and not msg.is_error_frame:
            if msg.arbitration_id in known_ids:
                try:
                    decoded = db.decode_message(msg.arbitration_id, msg.data)
                    for name, value in decoded.items():
                        latest[name] = value if isinstance(value, (int, float)) else str(value)
                except Exception as exc:
                    print("decode error", hex(msg.arbitration_id), exc)
            elif msg.arbitration_id in RAW_IDS:
                raw[f"{msg.arbitration_id:03X}"] = msg.data.hex()

        now = time.monotonic()
        if now >= next_publish and client.is_connected():
            next_publish = now + PUBLISH_INTERVAL
            if latest:
                payload = {"ts": round(time.time(), 3), "signals": latest}
                client.publish(f"{PREFIX}/signals", json.dumps(payload), qos=1)
            if raw:
                client.publish(f"{PREFIX}/raw", json.dumps(raw), qos=0)
except KeyboardInterrupt:
    pass
finally:
    client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True)
    client.loop_stop()
    client.disconnect()
    bus.shutdown()

Çalıştırın ve çıktıyı başka bir terminalden izleyin:

python gateway.py
mosquitto_sub -h broker.connectmq.com -p 1883 -i can-viewer-k7x2q9 -t "connectmq/demo/k7x2q9/vehicle1/#" -v

Gerçek bir veri yolu olmadan tüm zinciri sanal bir arayüzle test edebilirsiniz: sudo modprobe vcan && sudo ip link add dev vcan0 type vcan && sudo ip link set vcan0 up komutunu çalıştırın, kanalı vcan0 olarak değiştirin ve frame'leri cansend vcan0 1F0#0A1B2C3D ile enjekte edin.

Hız Sınırlama ve Topic Tasarımı

Betik, frame başına bir mesaj yerine saniyede bir birleşik anlık görüntü yayınlar. Bu, genel MQTT test broker'ının client başına saniyede 10 mesaj limitine uyar ve özel bir broker'ı da sakin tutar. Pratikte iyi çalışan bazı desenler:

  • Panolar için anlık görüntü topic'i: Her aralıkta çözülmüş tüm sinyalleri içeren tek bir JSON dokümanı.
  • Tüketiciler tekil değerlere abone olduğunda .../signals/EngineSpeed gibi sinyal başına topic'ler. Yalnızca değer bir ölü banttan fazla değiştiğinde yayınlayın.
  • Ayrık değişimler (hata kodları, kapı açık, durum makinesi geçişleri) için QoS 1 ile anında yayınlanan olay topic'leri.
  • Yalnızca incelenen ID'ler için ham topic, asla tüm veri yolu için değil.

Topic hiyerarşisini kararlı ve açıklayıcı tutun. Gateway bir tesisin parçasıysa site/area/line/machine/can gibi ISA-95 tarzı bir yol bir Unified Namespace'e doğal olarak oturur; bir Raspberry Pi IoT gateway de CAN verisini diğer kaynaklarla tek bir önek altında birleştirebilir.

Üretime Geçiş

Genel broker test içindir: orada yayınlanan her şey herkes tarafından okunabilir. Gerçek bir araç veya makine için ConnectMQ üzerinde özel bir broker oluşturun, üretilen kullanıcı adını, parolayı ve client ID'yi portaldan kopyalayın ve ortam değişkenlerini ayarlayın:

export MQTT_HOST=broker.connectmq.com
export MQTT_PORT=8883
export MQTT_USERNAME=YOUR_USERNAME
export MQTT_PASSWORD=YOUR_PASSWORD
export MQTT_CLIENT_ID=YOUR_CLIENT_ID
export TOPIC_PREFIX=fleet/truck-17/can
python gateway.py

8883 portu Let's Encrypt sertifikasıyla TLS kullanır; bu yüzden sistem CA paketiyle tls_set() yeterlidir. Tam yönetilen bir MQTT hizmeti, uçta kendi broker'ınızı çalıştırma ve yamalama ihtiyacını da ortadan kaldırır. Saha gateway'i için hücresel kopmaların boşluk yaratmaması adına bunu SQLite ile çevrimdışı tamponlama ile birleştirin ve betiği elektrik kesintisinden sonra başlaması için bir servis olarak çalıştırın.

Sorun Giderme

Belirti Olası neden Çözüm
can0 mevcut değil Overlay yüklenmemiş veya SPI kapalı config.txt ve dmesg çıktısını kontrol edin
Arayüz açık, frame yok Yanlış oscillator veya bit hızı Kristal ve veri yolu hızını eşleştirin
Hata sayaçları artıyor, BUS-OFF Eksik sonlandırma, H/L ters bağlı, yanlış bit hızı Güç kapalıyken H ve L arasında yaklaşık 60 ohm ölçün
Frame'ler alınıyor, çözme hataları DBC bu varyantla eşleşmiyor ID'leri ve bayt sırasını candump ile doğrulayın
Yük altında MQTT bağlantısı kopuyor Her frame yayınlanıyor Yukarıda gösterildiği gibi toplayın ve kısın
Değerler yanlış ölçeklenmiş görünüyor İşaretli/işaretsiz veya bayt sırası DBC'deki çarpanı, ofseti ve endianness'ı kontrol edin

Güvenlikle ilgili bir not: bu gateway yalnızca dinler. MCP2515 yine de veri yolundaki frame'leri onaylar (acknowledge); bu normal CAN davranışıdır. Ancak sonuçlarını tam olarak anlamadığınız sürece bir araç veya makine veri yoluna gönderim yapan kod eklemeyin. Kesinlikle pasif bir bağlantıya ihtiyacınız varsa kontrolörü sudo ip link set can0 up type can bitrate 500000 listen-only on ile yalnızca dinleme moduna alın.

Pi'ın bir MQTT client'ı olarak kullanımına dair daha geniş bir arka plan için Raspberry Pi MQTT eğitimi ile başlayın. HAT bir seçenek değilse python-can dokümantasyonu USB-CAN adaptörleri gibi diğer arayüzleri ele alır.

CAN Verinizi ConnectMQ'ya Aktarın

Frame'ler çözüldükten sonra zor kısım biter; geri kalanı güvenilir teslimattır. Kredi kartı gerekmeden app.connectmq.com adresinde ücretsiz bir ConnectMQ Serverless broker oluşturun, kimlik bilgilerini gateway'e yapıştırın ve sinyallerinizin canlı topic gezginine gelişini izleyin. Filo büyüdüğünde aynı kod değişiklik yapmadan daha büyük bir plana taşınır.

Sık Sorulan Sorular

HAT yerine USB-CAN adaptörü kullanabilir miyim?

Evet. gs_usb sürücüsünü kullananlar gibi Linux çekirdeği tarafından desteklenen adaptörler SocketCAN üzerinden can0 olarak görünür; bu yüzden Python betiği değişmeden çalışır. Diğer adaptörler kendi python-can arayüz adlarıyla kullanılabilir.

Bu CAN FD ile çalışır mı?

MCP2515 yalnızca klasik CAN'i destekler. CAN FD için MCP2518FD tabanlı bir HAT'e ve buna uygun overlay'e ihtiyacınız var; python-can ve cantools FD frame'lerini destekler.

OBD-II veya J1939 verisini nasıl çözerim?

OBD-II PID'leri istek/yanıt mantığıyla çalışır; bu yüzden yalnızca dinlemek yerine istek göndermeniz gerekir. J1939 29 bitlik tanımlayıcılar ve PGN'ler kullanır; açık DBC dosyaları birçok standart parametreyi kapsar.

Gateway kaç mesaj yayınlamalı?

Veri yolunun ürettiğini değil, tüketicilerin ihtiyaç duyduğunu yayınlayın. Saniyede bir anlık görüntü ve anında gönderilen olay mesajları iyi bir varsayılandır ve broker limitlerinin rahatça altında kalı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.