00 OBD-II nedir: yasal zorunluluk ve protokol ailesi
OBD-II (On-Board Diagnostics, ikinci nesil), araçlardaki emisyon sistemlerini izlemek ve arıza kodlarını teşhis etmek amacıyla geliştirilmiş standarttır. ABD'de 1996'dan, AB'de 2001'den itibaren zorunludur.
Yasal zemin
Fiziksel katman protokol ailesi
| Protokol | Standart | Hız | Durum |
|---|---|---|---|
| ISO 15765-4 (CAN) | ISO 15765 | 250k / 500k bit/s | Günümüz standardı (2008+ tüm araçlar) |
| ISO 9141-2 | ISO 9141 | 10.4 kbaud | Eski Avrupa ve Asya araçları (K-Line) |
| ISO 14230 (KWP2000) | ISO 14230 | 10.4 kbaud | Eski Avrupa araçları |
| SAE J1850 PWM | SAE J1850 | 41.6 kbaud | Eski Ford araçları |
| SAE J1850 VPW | SAE J1850 | 10.4 kbaud | Eski GM araçları |
2008 model yılından itibaren ABD'de satılan tüm araçlar ISO 15765-4 (CAN tabanlı OBD-II) kullanmak zorundadır. Eski protokoller ELM327 gibi multi-protokol adaptörlerle desteklenir.
01 ISO 15765-4: CAN tabanlı OBD-II
ISO 15765-4, CAN bus üzerinde OBD-II iletişimini tanımlar. 11-bit CAN ID ile çalışır; özel broadcast ve unicast adresleme şeması kullanır.
CAN ID şeması
| CAN ID | Yön | Açıklama |
|---|---|---|
| 0x7DF | Scan tool → ECU | Broadcast request — tüm ECU'lara |
| 0x7E0 | Scan tool → ECU | Engine ECU'ya hedefli istek |
| 0x7E1 | Scan tool → ECU | Transmission ECU hedefli |
| 0x7E2–0x7E7 | Scan tool → ECU | Diğer ECU'lar |
| 0x7E8 | ECU → Scan tool | Engine ECU yanıtı (0x7E0 + 8) |
| 0x7E9 | ECU → Scan tool | Transmission ECU yanıtı |
| 0x7EA–0x7EF | ECU → Scan tool | Diğer ECU yanıtları |
OBD-II istek-yanıt yapısı
İstek (Scan Tool → 0x7DF):
[02][01][0C][00][00][00][00][00]
│ │ │ └── padding (0x00)
│ │ └────── PID 0x0C (Engine RPM)
│ └────────── Mode 01 (current data)
└────────────── PCI byte: Single Frame, length=2
Yanıt (Engine ECU 0x7E8 →):
[04][41][0C][1A][F8][00][00][00]
│ │ │ └──── data (2 byte: 0x1AF8)
│ │ └──────── PID echo
│ └──────────── Mode 01 response (0x40 | 0x01 = 0x41)
└──────────────── PCI byte: Single Frame, length=4
RPM = (0x1A * 256 + 0xF8) / 4 = (6656 + 248) / 4 = 1726 RPM
02 Servis modları
OBD-II servis modları (service/mode byte), farklı teşhis bilgisi kategorilerine erişim sağlar. Mode 01-09 OBD-II standardında tanımlıdır; 0x10+ araç üreticisi tanımlıdır.
| Mode (hex) | İsim | Açıklama |
|---|---|---|
| 0x01 | Show current data | Gerçek zamanlı sensör verileri (PID ile sorgu) |
| 0x02 | Show freeze frame data | DTC oluştuğundaki anlık sensör snapshot'ı |
| 0x03 | Show DTCs | Aktif arıza kodları (Diagnostic Trouble Codes) |
| 0x04 | Clear DTCs | Arıza kodlarını ve freeze frame'leri temizle |
| 0x05 | O2 sensor monitoring | Oksijen sensörü test sonuçları (CAN'da kullanılmaz) |
| 0x06 | On-board monitoring | Sürekli izleme test sonuçları |
| 0x07 | Pending DTCs | Onaylanmamış (bekleyen) arıza kodları |
| 0x08 | Control operation | Aktüatör ve komponent testi |
| 0x09 | Vehicle information | VIN, kalibrasyon ID, CVN |
| 0x0A | Permanent DTCs | Temizlenemez kalıcı arıza kodları |
Mode 03: DTC format
def decode_dtc(b1: int, b2: int) -> str:
"""2 byte DTC'yi P0123 formatına çevir"""
systems = {0: 'P', 1: 'C', 2: 'B', 3: 'U'}
system = systems[(b1 >> 6) & 0x03]
digit1 = (b1 >> 4) & 0x03 # 0-3
digit2 = b1 & 0x0F
digit3 = (b2 >> 4) & 0x0F
digit4 = b2 & 0x0F
return ff"{system}{digit1}{digit2:X}{digit3:X}{digit4:X}"
# Örnek: 0x01, 0x13 → P0113 (IAT Sensor High Input)
print(decode_dtc(0x01, 0x13)) # P0113
print(decode_dtc(0x43, 0x02)) # P0302 (Cylinder 2 Misfire)
03 PID sistemi ve formüller
Mode 01 PID'leri, gerçek zamanlı araç verilerine erişim sağlar. Her PID'in veri uzunluğu ve hesaplama formülü standartta belirtilmiştir.
Mode 01 PID 0x00: Desteklenen PID bitmask
PID 0x00 sorgusu, ECU'nun hangi PID'leri desteklediğini 32-bit bitmask olarak döner. Bit 31 = PID 0x01, bit 0 = PID 0x20.
def decode_supported_pids(data: bytes, base_pid: int = 0x00) -> list:
"""PID 0x00/0x20/0x40 yanıtından desteklenen PID listesi çıkar"""
bitmap = int.from_bytes(data[:4], 'big')
supported = []
for bit in range(32):
if bitmap & (1 << (31 - bit)):
supported.append(base_pid + bit + 1)
return supported
# Örnek yanıt: 0xBE1FA813
pids = decode_supported_pids(bytes([0xBE, 0x1F, 0xA8, 0x13]))
print(", ".join(ff"0x{p:02X}" for p in pids))
Yaygın PID tablosu ve formüller
| PID | İsim | Byte | Formül | Birim |
|---|---|---|---|---|
| 0x04 | Engine Load | A | A × 100 / 255 | % |
| 0x05 | Coolant Temperature | A | A - 40 | °C |
| 0x0B | Intake Manifold Pressure | A | A | kPa |
| 0x0C | Engine RPM | A, B | (256A + B) / 4 | rpm |
| 0x0D | Vehicle Speed | A | A | km/h |
| 0x0E | Timing Advance | A | A / 2 - 64 | ° before TDC |
| 0x0F | Intake Air Temperature | A | A - 40 | °C |
| 0x10 | MAF Air Flow Rate | A, B | (256A + B) / 100 | g/s |
| 0x11 | Throttle Position | A | A × 100 / 255 | % |
| 0x2F | Fuel Tank Level | A | A × 100 / 255 | % |
| 0x46 | Ambient Air Temperature | A | A - 40 | °C |
| 0x5C | Engine Oil Temperature | A | A - 40 | °C |
PID_DECODERS = {
0x04: ('Engine Load %', lambda d: d[0] * 100 / 255),
0x05: ('Coolant Temp °C', lambda d: d[0] - 40),
0x0C: ('Engine RPM', lambda d: (d[0] * 256 + d[1]) / 4),
0x0D: ('Vehicle Speed km/h', lambda d: d[0]),
0x0F: ('Intake Air Temp °C', lambda d: d[0] - 40),
0x10: ('MAF g/s', lambda d: (d[0] * 256 + d[1]) / 100),
0x11: ('Throttle %', lambda d: d[0] * 100 / 255),
0x2F: ('Fuel Level %', lambda d: d[0] * 100 / 255),
0x5C: ('Oil Temp °C', lambda d: d[0] - 40),
}
def decode_pid(pid: int, raw_data: bytes):
if pid in PID_DECODERS:
name, formula = PID_DECODERS[pid]
value = formula(raw_data)
print(ff"PID 0x{pid:02X} {name}: {value:.2f}")
else:
print(ff"PID 0x{pid:02X}: {raw_data.hex()}")
04 ISO-TP (ISO 15765-2): transport katmanı
ISO-TP (ISO 15765-2), 8 byte CAN limiti üzerinde veri taşımak için segmentasyon ve akış kontrolü sağlar. OBD-II, UDS ve AUTOSAR bu katman üzerine inşa edilir.
ISO-TP frame tipleri
| Frame Tipi | PCI byte (nibble) | Açıklama |
|---|---|---|
| Single Frame (SF) | 0x0N (N=0-7) | N byte veri, tek CAN frame'e sığıyor |
| First Frame (FF) | 0x1L (12-bit len) | Çok parçalı mesajın ilk frame'i |
| Consecutive Frame (CF) | 0x2N (N=sıra no) | Devam frame'leri |
| Flow Control (FC) | 0x3X | Alıcının akış kontrolü (CTS/WT/OVFLW) |
Çok frame'li mesaj akışı
Gönderen (0x7E0) Alıcı (0x7E8)
→ [10][0F][41][03]... First Frame: 15 byte mesaj
← [30][00][00] Flow Control: CTS, BlockSize=0 (sınırsız)
→ [21][xx][xx][xx][xx][xx][xx][xx] CF #1
→ [22][xx][xx][xx][xx][xx][xx][xx] CF #2 (son)
PCI byte açıklaması:
10 0F = FF, total length = 15
30 00 00 = FC, ContinueToSend, BlockSize=0, SeparationTime=0ms
21 = CF sıra no = 1
22 = CF sıra no = 2
Linux isotp socket modülü
# ISO-TP kernel modülü yükle
sudo modprobe can-isotp
# Modülün yüklendiğini doğrula
lsmod | grep isotp
# isotp socket kullanımı: isotpsend / isotprecv araçları
# isotp-utils paketi (can-utils ile birlikte gelir)
# OBD Mode 01 PID 0x0C isteği gönder (tek frame)
# TX ID: 0x7DF (broadcast), RX ID: 0x7E8 (engine ECU yanıtı)
sudo isotpsend -s 7DF -d 7E8 can0 <<< "01 0C"
# Yanıt al
sudo isotprecv -s 7E8 -d 7DF can0
05 ELM327 ve türevleri
ELM327, OBD-II protokollerini soyutlayan popüler bir mikrodenetleyici chipsettir. AT komutları ile yapılandırılır; çoğu araç teşhis adaptörünün temelini oluşturur.
Temel AT komutları
| AT Komutu | İşlev |
|---|---|
| AT Z | Sıfırla ve ELM327 sürümünü yazdır |
| AT E0 | Echo kapalı (temiz çıktı için) |
| AT L0 | Satır sonu (linefeed) kapalı |
| AT H1 | CAN header (ID) göster |
| AT SP 6 | Protokol 6 seç: ISO 15765-4 CAN (11-bit, 500k) |
| AT SP 0 | Protokolü otomatik algıla |
| AT DP | Aktif protokolü göster |
| AT RV | Araç pil voltajını ölç |
| 01 0C | Mode 01, PID 0x0C (RPM) sorgula |
| 03 | Mode 03: DTC listesi oku |
ELM327 protokol seçenekleri (AT SP)
| SP değeri | Protokol |
|---|---|
| 0 | Otomatik algıla |
| 3 | ISO 9141-2 |
| 4, 5 | ISO 14230-4 (KWP2000) |
| 6 | ISO 15765-4 CAN (11-bit, 500 kbps) |
| 7 | ISO 15765-4 CAN (29-bit, 500 kbps) |
| 8 | ISO 15765-4 CAN (11-bit, 250 kbps) |
| 9 | ISO 15765-4 CAN (29-bit, 250 kbps) |
Arduino / Bluetooth / WiFi ELM327 dongle
import serial, time
class ELM327:
def __init__(self, port: str, baud: int = 38400):
self.ser = serial.Serial(port, baud, timeout=2)
self._init()
def _init(self):
self.cmd('AT Z') # reset
time.sleep(1)
self.cmd('AT E0') # echo off
self.cmd('AT L0') # linefeed off
self.cmd('AT H1') # headers on
self.cmd('AT SP 0') # auto protocol
def cmd(self, command: str) -> str:
self.ser.write((command + '\r').encode())
time.sleep(0.1)
resp = self.ser.read_all().decode('ascii', errors='ignore')
return resp.strip().rstrip('>').strip()
def get_rpm(self) -> float:
resp = self.cmd('01 0C')
# Yanıt örneği: "7E8 04 41 0C 1A F8"
parts = resp.split()
if '41' in parts:
idx = parts.index('41') + 2 # PID echo sonrası
return (int(parts[idx], 16) * 256 + int(parts[idx+1], 16)) / 4
return -1
elm = ELM327('/dev/rfcomm0') # Bluetooth port
print(ff"RPM: {elm.get_rpm()}")
06 python-obd kütüphanesi
python-obd, ELM327 tabanlı adaptörler için yüksek seviyeli Python API sunar. PID tanımlamaları, birim dönüşümleri ve async izleme desteği içerir.
pip install obd
import obd
# Otomatik port algılamayla bağlan
connection = obd.OBD() # veya obd.OBD('/dev/rfcomm0')
if connection.is_connected():
print("OBD-II bağlantısı kuruldu")
# Anlık sorgu
rpm_resp = connection.query(obd.commands.RPM)
speed_resp = connection.query(obd.commands.SPEED)
temp_resp = connection.query(obd.commands.COOLANT_TEMP)
if not rpm_resp.is_null():
print(ff"RPM: {rpm_resp.value.magnitude:.0f}")
if not speed_resp.is_null():
print(ff"Hız: {speed_resp.value.to('km/h').magnitude:.1f} km/h")
if not temp_resp.is_null():
print(ff"Soğutucu Sıcaklığı: {temp_resp.value.magnitude:.1f}°C")
connection.close()
Async mod ve watch()
import obd, time
# Asenkron bağlantı
connection = obd.Async()
# Callback ile PID izle
def on_rpm(response):
if not response.is_null():
print(ff"RPM: {response.value.magnitude:.0f}")
def on_speed(response):
if not response.is_null():
print(ff"Hız: {response.value.to('km/h').magnitude:.1f}")
connection.watch(obd.commands.RPM, callback=on_rpm)
connection.watch(obd.commands.SPEED, callback=on_speed)
connection.start()
time.sleep(30) # 30 saniye izle
connection.stop()
connection.close()
DTC okuma ve temizleme
import obd
conn = obd.OBD()
# Aktif DTC'leri oku (Mode 03)
dtc_response = conn.query(obd.commands.GET_DTC)
if not dtc_response.is_null():
for code, desc in dtc_response.value:
print(ff"DTC: {code} {desc}")
else:
print("DTC yok")
# Arıza kodlarını temizle (Mode 04) — DİKKATLİ KULLAN!
# clear_resp = conn.query(obd.commands.CLEAR_DTC)
# if clear_resp.is_null():
# print("Temizleme başarısız")
# else:
# print("DTC'ler temizlendi")
conn.close()
Custom command tanımlama
import obd
from obd import OBDCommand
from obd.protocols import ECU
from obd.utils import bytes_to_int
def decode_oil_temp(messages):
d = messages[0].data[2:]
if len(d) < 1:
return obd.OBDResponse()
temp = bytes_to_int(d) - 40
return obd.Unit.Quantity(temp, obd.Unit.celsius)
# PID 0x5C: Engine Oil Temperature (Mode 01)
OIL_TEMP = OBDCommand(
name = "OIL_TEMP",
desc = "Engine Oil Temperature",
command = b"\x01\x5C",
_bytes = 3,
decoder = decode_oil_temp,
ecu = ECU.ENGINE,
fast = True
)
conn = obd.OBD()
resp = conn.query(OIL_TEMP)
if not resp.is_null():
print(ff"Motor Yağ Sıcaklığı: {resp.value}°C")
conn.close()
07 Linux SocketCAN + isotp ile OBD-II
ELM327 soyutlaması yerine, SocketCAN + ISO-TP socket ile doğrudan CAN katmanından OBD-II sorgusu gönderilebilir. Bu yöntem daha düşük gecikme ve tam kontrol sağlar.
isotp kernel modülü ve araçlar
# ISO-TP modülü yükle
sudo modprobe can-isotp
# Araç bağlantısı (OBD konnektör → PEAK PCAN-USB veya MCP2515)
sudo ip link set can0 type can bitrate 500000 restart-ms 100
sudo ip link set can0 up
# RPM sorgusu: Mode 01, PID 0x0C
# TX: 0x7DF broadcast, RX: 0x7E8 (engine ECU yanıtı)
sudo isotpsend -s 7DF -d 7E8 can0 <<< "01 0C"
# Yanıt alma (ayrı terminal)
sudo isotprecv -s 7E8 -d 7DF can0
# Çıktı: 41 0C 1A F8 (41=Mode 01 yanıt, 0C=PID echo, 1AF8=RPM raw)
# DTC sorgusu (Mode 03 — no PID)
sudo isotpsend -s 7DF -d 7E8 can0 <<< "03"
C ile ham OBD-II sorgusu (ISO-TP socket)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/isotp.h>
int main(void) {
int s = socket(AF_CAN, SOCK_DGRAM, CAN_ISOTP);
struct sockaddr_can addr;
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_addr.tp.tx_id = 0x7DF; /* OBD broadcast */
addr.can_addr.tp.rx_id = 0x7E8; /* Engine ECU yanıtı */
addr.can_ifindex = if_nametoindex("can0");
bind(s, (struct sockaddr *)&addr, sizeof(addr));
/* Mode 01 PID 0x0C isteği */
uint8_t req[] = {0x01, 0x0C};
write(s, req, sizeof(req));
/* Yanıt oku */
uint8_t resp[8];
ssize_t len = read(s, resp, sizeof(resp));
if (len >= 4 && resp[0] == 0x41 && resp[1] == 0x0C) {
float rpm = ((resp[2] * 256.0f) + resp[3]) / 4.0f;
printf("Motor Hızı: %.1f RPM\n", rpm);
}
close(s);
return 0;
}
08 Pratik: araç veri loggeri ve dashboard
Raspberry Pi + ELM327 (Bluetooth veya USB) kombinasyonu ile gerçek zamanlı araç verisi kaydedilebilir ve görselleştirilebilir.
Raspberry Pi + ELM327 Bluetooth kurulumu
# Bluetooth servis aktif mi?
sudo systemctl status bluetooth
# Bluetooth cihazları tara
sudo bluetoothctl
# power on
# scan on
# pair XX:XX:XX:XX:XX:XX (ELM327 MAC adresi)
# trust XX:XX:XX:XX:XX:XX
# RFCOMM seri port oluştur
sudo rfcomm bind 0 XX:XX:XX:XX:XX:XX 1
# /dev/rfcomm0 oluştu
# Test
sudo python3 -c "import obd; print(obd.OBD('/dev/rfcomm0').status())"
Gerçek zamanlı dashboard (python-obd + matplotlib)
import obd, time, collections
import matplotlib.pyplot as plt
import matplotlib.animation as animation
MAX_POINTS = 60
rpm_history = collections.deque(maxlen=MAX_POINTS)
speed_history = collections.deque(maxlen=MAX_POINTS)
conn = obd.Async()
conn.watch(obd.commands.RPM)
conn.watch(obd.commands.SPEED)
conn.start()
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
fig.suptitle('Araç Verileri — OBD-II')
def update(frame):
rpm_r = conn.query(obd.commands.RPM)
speed_r = conn.query(obd.commands.SPEED)
if not rpm_r.is_null():
rpm_history.append(rpm_r.value.magnitude)
if not speed_r.is_null():
speed_history.append(speed_r.value.to('km/h').magnitude)
ax1.clear()
ax1.plot(list(rpm_history), color='#E74C3C', linewidth=2)
ax1.set_ylabel('RPM'); ax1.set_ylim(0, 8000)
ax1.set_title(ff"RPM: {rpm_history[-1]:.0f}" if rpm_history else 'RPM')
ax2.clear()
ax2.plot(list(speed_history), color='#2ECC71', linewidth=2)
ax2.set_ylabel('Hız (km/h)'); ax2.set_ylim(0, 200)
ax2.set_title(ff"Hız: {speed_history[-1]:.1f} km/h" if speed_history else 'Hız')
ani = animation.FuncAnimation(fig, update, interval=500)
plt.tight_layout()
plt.show()
conn.stop()
conn.close()
VIN decode
import obd
conn = obd.OBD()
# Mode 09 PID 0x02: VIN
vin_resp = conn.query(obd.commands.VIN)
if not vin_resp.is_null():
vin = vin_resp.value
print(ff"VIN: {vin}")
# VIN decode (basit)
countries = {'1': 'ABD', '2': 'Kanada', '3': 'Meksika',
'4': 'ABD', 'J': 'Japonya', 'K': 'Güney Kore',
'S': 'İngiltere', 'W': 'Almanya', 'V': 'Fransa'}
if len(vin) == 17:
wmi = vin[:3]
model_year_char = vin[9]
seq_no = vin[11:]
country = countries.get(vin[0], 'Bilinmiyor')
print(ff"Ülke: {country}, WMI: {wmi}, Sıra: {seq_no}")
conn.close()
Fleet monitoring giriş
import obd, time, json, requests
FLEET_API = "https://your-fleet-api.example.com/telemetry"
conn = obd.Async()
conn.watch(obd.commands.RPM)
conn.watch(obd.commands.SPEED)
conn.watch(obd.commands.COOLANT_TEMP)
conn.watch(obd.commands.FUEL_STATUS)
conn.start()
def collect_and_send():
while True:
data = {
'timestamp': time.time(),
'rpm': None, 'speed_kmh': None,
'coolant_c': None,
}
r = conn.query(obd.commands.RPM)
if not r.is_null(): data['rpm'] = r.value.magnitude
r = conn.query(obd.commands.SPEED)
if not r.is_null(): data['speed_kmh'] = r.value.to('km/h').magnitude
r = conn.query(obd.commands.COOLANT_TEMP)
if not r.is_null(): data['coolant_c'] = r.value.magnitude
try:
requests.post(FLEET_API, json=data, timeout=2)
except Exception:
pass # bağlantı kesintisinde logla, devam et
time.sleep(5) # 5 saniyede bir gönder
collect_and_send()
OBD-II araç verilerine erişim yasal olarak izin verilse de bazı araç üreticileri (özellikle 2022+ modeller) araç sürücüsünün OBD portuna erişimini kısıtlamaktadır. Mode 04 (DTC Temizle) komutu emisyon monitörlerini sıfırlar; araç muayenesi öncesi kullanılmamalıdır.