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:
- Linux SocketCAN üzerinden veri yolundan ham frame'leri almak.
- İlgilendiğiniz frame'leri adlandırılmış fiziksel değerlere (rpm, derece, volt) çözmek.
- Veri hızını düşürmek, çünkü bir CAN veri yolu saniyede binlerce frame taşıyabilir.
- 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/EngineSpeedgibi 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.



