00 NMEA 2000 nedir: deniz elektroniği standardı
NMEA 2000 (N2K), deniz elektroniği cihazlarının tek bir kablo sistemi üzerinde haberleşmesini sağlayan standarttır. IEC 61162-3 olarak da bilinir ve SAE J1939 temeli üzerine inşa edilmiştir.
Genel özellikler
NMEA 0183 ile fark
| Özellik | NMEA 0183 | NMEA 2000 |
|---|---|---|
| Topoloji | Point-to-point RS-422 | Multi-drop bus (CAN) |
| Hız | 4800 / 38400 baud | 250 kbit/s |
| Cihaz sayısı | 1 verici, çok alıcı | 50+ cihaz (LEN sınırı) |
| Format | ASCII metin ($GPRMC,...) | Binary PGN |
| Güç hattı | Yok | Bus'ta 12V |
| Çift yön | Hayır | Evet |
Her N2K cihazının belirtilen bir LEN değeri vardır. LEN, cihazın bus'tan çektiği akımın göstergesidir (1 LEN = 1mA nominal). Tek bir backbone segmentindeki toplam LEN değeri 50'yi aşmamalıdır.
01 Fiziksel katman: kablo, terminatör, güç
NMEA 2000, DeviceNet kablo standardını (IEC 62026-3) kullanır. 5 iletkenli özel kablo, terminatörler ve T-konnektörler belirli boyutlarda üretilir.
Kablo renk kodu (DeviceNet)
İletken Renk İşlev
────────────────────────────────
1 Kırmızı +12V (Bus güç)
2 Siyah GND
3 Beyaz CAN_H
4 Mavi CAN_L
5 Dray CAN Shield (toprak)
Kablo boyutları
| Kablo Tipi | Güç iletkeni | CAN iletkeni | Maks. uzunluk |
|---|---|---|---|
| Mini (trunk) | 0.5 mm² | 0.35 mm² | Backbone 100m, Drop 6m |
| Micro (drop) | 0.35 mm² | 0.13 mm² | Sadece drop, maks. 1m |
| Mid (trunk) | 1.0 mm² | 0.35 mm² | Backbone 100m, Drop 6m |
Topoloji ve uzunluk limitleri
[Terminatör] ──── Backbone ──── [T] ──── Backbone ──── [Terminatör]
│
Drop (maks. 6m)
│
Cihaz (chartplotter, GPS vb.)
Backbone maks.: 100m (toplam)
Drop maks.: 6m (Mini/Mid kablo)
Drop maks.: 1m (Micro kablo)
Cihaz sayısı: 50+ LEN birimi (pratik: 20-30 cihaz)
Terminatörler
Backbone'un her iki ucuna 120Ω terminatör takılmalıdır. Aktif terminatörler (120Ω + dahili güç) bazı sistemlerde backbone güç dağıtımını düzenler.
İki terminatör yoksa veya yanlış konumdaysa bus düzgün çalışmaz. Terminatör durumunu kontrol etmek için CANBOAT veya Actisense NMEA Reader gibi araçlarla "Bus diagnostics" bakın. Multimetreyle CAN_H — CAN_L arasındaki DC direnç ~60Ω olmalıdır.
02 PGN yapısı ve Fast Packet protokolü
NMEA 2000, J1939'dan türetilmiş PGN yapısını kullanır. 8 byte'tan büyük veriler için özel Fast Packet protokolü tanımlanmıştır; bu J1939 TP'dan farklıdır.
29-bit CAN ID yapısı (J1939 ile aynı)
Bit 28-26: Priority (0-7)
Bit 25: Reserved
Bit 24: Data Page (DP)
Bit 23-16: PDU Format (PF)
Bit 15-8: PDU Specific veya Group Extension (PS/GE)
Bit 7-0: Source Address (SA)
PGN = DP(1) | PF(8) | PS/GE(8) — PDU2 için
PDU1 (PF < 0xF0): hedefli, PS = Destination Address
PDU2 (PF ≥ 0xF0): broadcast, PS = Group Extension
Fast Packet protokolü
8 byte'tan büyük N2K mesajları (örneğin 32 byte GNSS pozisyon verisi) Fast Packet ile gönderilir. Fast Packet, J1939 TP'dan farklı, N2K'ya özgü segmentasyon yöntemidir:
Frame 1 (First Frame):
[Seq+Frame#][Total bytes][data bytes 0-5] = 6 byte veri
Byte 0: bits 7-5 = Sequence Counter (0-7)
bits 4-0 = Frame Counter (0 = first frame)
Frame 2-N (Subsequent Frames):
[Seq+Frame#][data bytes 0-6] = 7 byte veri
Byte 0: bits 7-5 = same Sequence Counter
bits 4-0 = Frame Counter (1, 2, 3...)
Sequence Counter: 0-7 arası döngüsel, aynı PGN'nin farklı mesajlarını ayırt eder
from typing import Optional
class FastPacketAssembler:
"""NMEA 2000 Fast Packet frame'lerini birleştirir"""
def __init__(self):
self.buffers = {} # (pgn, sa, seq) → {'total': N, 'data': bytearray}
def feed(self, pgn: int, sa: int, raw: bytes) -> Optional[bytes]:
header = raw[0]
seq_cnt = (header >> 5) & 0x07
frame_no = header & 0x1F
key = (pgn, sa, seq_cnt)
if frame_no == 0: # First frame
total = raw[1]
data = bytearray(raw[2:])
self.buffers[key] = {'total': total, 'data': data}
return None
else: # Subsequent frame
if key not in self.buffers:
return None # first frame kaçırıldı
buf = self.buffers[key]
buf['data'].extend(raw[1:])
if len(buf['data']) >= buf['total']:
complete = bytes(buf['data'][:buf['total']])
del self.buffers[key]
return complete
return None
03 Önemli NMEA 2000 PGN'leri
NMEA 2000, deniz uygulamaları için yüzlerce PGN tanımlar. En yaygın kullanılanlar konum, yönelim ve makine verileridir.
| PGN | İsim | Veri | Periyot |
|---|---|---|---|
| 127250 | Vessel Heading | Yön (radyan), referans (Mag/True), sapma | 100 ms |
| 127251 | Rate of Turn | Dönüş hızı (rad/s) | 100 ms |
| 127245 | Rudder | Dümen açısı | 100 ms |
| 127488 | Engine Parameters Rapid | RPM, boost pressure, tilt | 100 ms |
| 127489 | Engine Parameters Dynamic | Yağ basıncı, su sıcaklığı, alternatör voltajı | 500 ms |
| 128259 | Speed | Tekne hızı (su / yer) | 100 ms |
| 128267 | Water Depth | Su derinliği, offset, menzil | 1000 ms |
| 129025 | Position Rapid Update | Enlem, boylam (1/1e7 derece) | 100 ms |
| 129026 | COG SOG Rapid Update | Rota (COG) ve zemin hızı (SOG) | 100 ms |
| 129029 | GNSS Position Data | Tam GPS verisi (zaman, konum, yükseklik, DOP) | 1000 ms |
| 130306 | Wind Data | Rüzgar hızı, açısı (app/true) | 100 ms |
| 130311 | Environmental Parameters | Su sıcaklığı, hava sıcaklığı, basınç | 500 ms |
| 129794 | AIS Class A Static Data | MMSI, IMO, tekne adı, boyut | Event |
| 129038 | AIS Class A Position | MMSI, konum, COG, SOG, rot | Event |
PGN 127250 (Vessel Heading) parse
import struct, math
def parse_vessel_heading(data: bytes) -> dict:
"""PGN 127250 — 8 byte"""
if len(data) < 8:
return {}
sid = data[0]
heading_raw = struct.unpack_from('<H', data, 1)[0]
deviation = struct.unpack_from('<h', data, 3)[0] # signed
variation = struct.unpack_from('<h', data, 5)[0] # signed
reference = (data[7] >> 6) & 0x03 # 0=mag, 1=true
def rad_to_deg(raw, scale=1e-4):
if raw == 0xFFFF or raw == 0x7FFF:
return None
return math.degrees(raw * scale)
return {
'sid': sid,
'heading_deg': rad_to_deg(heading_raw),
'deviation_deg': rad_to_deg(deviation) if deviation != -32768 else None,
'variation_deg': rad_to_deg(variation) if variation != -32768 else None,
'reference': ['Magnetic', 'True', 'Error', 'Null'][reference],
}
# Test
sample = bytes([0x01, 0x2E, 0x04, 0xFF, 0x7F, 0xFF, 0x7F, 0x40])
result = parse_vessel_heading(sample)
print(ff"Yön: {result['heading_deg']:.1f}° {result['reference']}")
PGN 129025 (Position Rapid Update) parse
import struct
def parse_position_rapid(data: bytes) -> dict:
"""PGN 129025 — 8 byte: lat + lon"""
if len(data) < 8:
return {}
lat_raw = struct.unpack_from('<i', data, 0)[0] # signed 32-bit
lon_raw = struct.unpack_from('<i', data, 4)[0]
# Çözünürlük: 1e-7 derece / bit
lat = lat_raw * 1e-7
lon = lon_raw * 1e-7
return {'lat': lat, 'lon': lon}
# 41.0082° N, 28.9784° E (İstanbul)
lat_raw = int(41.0082 * 1e7)
lon_raw = int(28.9784 * 1e7)
sample = struct.pack('<ii', lat_raw, lon_raw)
pos = parse_position_rapid(sample)
print(ff"Konum: {pos['lat']:.4f}°N, {pos['lon']:.4f}°E")
04 ISO 11783 address claiming
NMEA 2000 adres yönetimi, J1939/81 ile birebir uyumludur. Aynı NAME ve address claiming mekanizması kullanılır.
N2K NAME alanı — deniz uygulamasına göre
Industry Group: 4 = Marine
Vehicle System (N2K'da "Device Class"):
0 = Not specified
10 = System tools (PC, gateway)
20 = Helm Navigation and Control
25 = Sensor Communication Interface
35 = Instrumentation / General Systems
40 = Electrical Distribution
50 = Propulsion Systems
60 = Navigation Systems
70 = Communication Systems
80 = Autopilot Systems
Adres claiming Python örneği (N2K için)
import struct, socket
def build_n2k_name(
manufacturer_code: int, # 11-bit (0x1ED = Simrad, 0x069 = Garmin...)
identity_number: int, # 21-bit (cihaz seri no)
device_instance: int = 0,
device_function: int = 60, # 60 = Navigation (GPS)
device_class: int = 60, # 60 = Navigation Systems
industry_group: int = 4, # 4 = Marine
arbitrary: bool = True
) -> int:
name = (
(int(arbitrary) << 63) |
((industry_group & 0x07) << 60) |
((device_class & 0x7F) << 49) |
((device_function & 0xFF) << 41) |
((device_instance & 0x07) << 38) |
((manufacturer_code & 0x7FF) << 21) |
(identity_number & 0x1FFFFF)
)
return name
# Raspberry Pi gateway için NAME
name = build_n2k_name(
manufacturer_code=0x001, # özel/test
identity_number=0x00001,
device_function=10, # System Tools
device_class=10,
industry_group=4 # Marine
)
print(ff"N2K NAME: 0x{name:016X}")
print(ff"Bytes: {struct.pack('<Q', name).hex()}")
05 CANBOAT araçları
CANBOAT (https://github.com/canboat/canboat), NMEA 2000 verilerini decode etmek için açık kaynak araç setidir. Hem binary PGN'leri analiz eder hem de JSON çıktı üretir.
CANBOAT kurulumu
# Bağımlılıklar
sudo apt install build-essential git
# Kaynak kodu indir ve derle
git clone https://github.com/canboat/canboat.git
cd canboat
make
sudo make install
# Kurulu araçlar:
# analyzer — PGN decode ve JSON çıktı
# n2kd — N2K daemon (TCP ile paylaşım)
# candump2analyzer — candump log → analyzer pipeline
# actisense-serial — Actisense NGT-1 USB adaptörü desteği
analyzer ile PGN decode
# candump çıktısını analyzer'a pipe et
candump can0 | analyzer
# JSON formatında çıktı
candump can0 | analyzer -json
# Çıktı örneği:
# {"timestamp":"2026-04-12T10:30:00.123Z","prio":2,
# "src":5,"dst":255,"pgn":129025,
# "description":"Position Rapid Update",
# "fields":{"Latitude":41.0082,"Longitude":28.9784}}
# Belirli PGN'i filtrele (bash ile)
candump can0 | analyzer -json | grep '"pgn":129025'
# candump log dosyasından analiz
candump2analyzer < candump_log.txt | analyzer -json
# Tüm bilinen PGN listesi
analyzer -explain
n2kd daemon
# n2kd: N2K verilerini TCP üzerinden paylaş
# can0 → TCP 2597 (analyzer JSON) + TCP 2598 (raw)
candump can0 | analyzer -json | n2kd -s 2597
# İstemci bağlantısı (başka cihazdan)
nc 192.168.1.100 2597
# JSON stream başlar
06 Linux ile NMEA 2000 okuma
Linux SocketCAN ile N2K bus'una bağlanmak için gerekli tek şey uyumlu CAN adaptörü ve CAN kernel modülleridir. Ardından candump + CANBOAT ile veri okunabilir.
Donanım adaptörleri
SocketCAN ile N2K veri okuma
# N2K 250 kbit/s
sudo ip link set can0 type can bitrate 250000 restart-ms 200
sudo ip link set can0 up
# Ham frame'leri izle (29-bit EFF → -e flag)
candump can0
# Örnek N2K çıktı:
# (1716890123.456) can0 09F80105 [8] 2E 04 FF 7F FF 7F 40 FF
# → 0x09F80105 = Priority=2, PGN=0x1F801=129025(pos), SA=0x05
# CANBOAT pipeline ile decode
candump can0 | analyzer -json | python3 -c "
import sys, json
for line in sys.stdin:
try:
msg = json.loads(line)
if msg.get('pgn') in [129025, 127250, 128259]:
print(json.dumps(msg['fields'], indent=2))
except: pass
"
Python ile doğrudan N2K okuma
import socket, struct
# J1939 socket ile N2K okuma
s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
s.bind(('can0',))
def decode_pgn_from_eff(can_id: int) -> tuple:
"""29-bit CAN ID'den PGN ve SA çıkar"""
can_id &= 0x1FFFFFFF # EFF flag temizle
priority = (can_id >> 26) & 0x07
dp = (can_id >> 24) & 0x01
pf = (can_id >> 16) & 0xFF
ps = (can_id >> 8) & 0xFF
sa = (can_id >> 0) & 0xFF
if pf < 0xF0:
pgn = (dp << 16) | (pf << 8)
else:
pgn = (dp << 16) | (pf << 8) | ps
return pgn, sa, priority
INTERESTING_PGNS = {129025, 127250, 128259, 127488, 130306}
while True:
frame = s.recv(16)
can_id, dlc = struct.unpack_from('<IB', frame, 0)
data = frame[8:8+dlc]
pgn, sa, prio = decode_pgn_from_eff(can_id)
if pgn in INTERESTING_PGNS:
print(ff"PGN={pgn:6d} SA={sa:#04x} prio={prio} data={data.hex()}")
07 OpenCPN ve Signal K entegrasyonu
Signal K, deniz verileri için açık kaynak JSON tabanlı veri modelidir. N2K → Signal K köprüsü, OpenCPN gibi chart plotter yazılımlarına veri akışı sağlar.
Signal K Server kurulumu
# Node.js gereklidir
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejs
# Signal K Server kurulumu
sudo npm install -g @signalk/server
# Başlat
signalk-server
# Web arayüzü: http://raspberrypi:3000
# Admin: http://raspberrypi:3000/@signalk/server-admin-ui
Signal K Server N2K yapılandırması
Signal K Server admin arayüzünden CAN data bağlantısı yapılandırılır:
# Server data bağlantısı için settings.json'a eklenir:
# Pipedeprovider: candump → analyzer -json → signal k
# Alternatif: canboat-to-signalk paketi
sudo npm install -g @canboat/canboatjs
# candump çıktısını SignalK'ya pipe et (manuel)
candump can0 | analyzer -json | \
node /usr/local/lib/node_modules/@canboat/canboatjs/tools/fromPgn.js | \
nc localhost 3000
Signal K API ile veri okuma
import requests, json
SIGNALK_URL = "http://localhost:3000/signalk/v1/api/vessels/self"
def get_vessel_data():
resp = requests.get(SIGNALK_URL, timeout=2)
data = resp.json()
# Konum
nav = data.get('navigation', {})
pos = nav.get('position', {}).get('value', {})
lat = pos.get('latitude')
lon = pos.get('longitude')
# Yön
heading = nav.get('headingMagnetic', {}).get('value')
if heading:
import math
heading = math.degrees(heading)
# Hız
sog = nav.get('speedOverGround', {}).get('value')
if sog:
sog_kn = sog * 1.944 # m/s → knot
print(ff"Konum: {lat:.4f}°N, {lon:.4f}°E")
print(ff"Yön: {heading:.1f}° SOG: {sog_kn:.1f} kn")
get_vessel_data()
OpenCPN konfigürasyonu
OpenCPN, Signal K veya NMEA 0183 TCP/UDP stream ile beslenir. Signal K server kuruluysa OpenCPN'de Signal K plugin ile bağlantı kurulabilir.
# OpenCPN kurulumu
sudo apt install opencpn
# OpenCPN içinden: Options → Connections → Add Signal K
# Host: localhost, Port: 3000
# Veya NMEA 0183 TCP → Signal K NMEA0183 output aktif et
# NMEA 0183 çıktısı için Signal K'ya NMEA 0183 output eklenir
# Settings → Signal paths → Enable NMEA 0183 output → port 10110
# OpenCPN: Options → Connections → TCP, localhost:10110
08 Pratik: Raspberry Pi tekne veri ağ geçidi
Raspberry Pi 4 + USB-CAN adaptörü (veya SPI-MCP2515) ile teknenin N2K bus'undaki GPS, pusula ve makine verileri WiFi üzerinden tablet veya telefona aktarılabilir.
Sistem mimarisi
NMEA 2000 Bus
│
[Actisense NGT-1 USB] veya [MCP2515 SPI → N2K kablo]
│ │
Raspberry Pi 4 ──────────────────────
│
candump | analyzer -json
│
Signal K Server (TCP 3000)
│
WiFi Access Point (hostapd)
│
Tablet / Telefon
(OpenCPN, iNavX, Aqua Map)
Tam kurulum scripti
#!/bin/bash
# NMEA 2000 → WiFi Gateway kurulum scripti
# 1. CAN interface başlat (Actisense NGT-1 veya MCP2515 sonrası)
sudo ip link set can0 type can bitrate 250000 restart-ms 200
sudo ip link set can0 up
echo "CAN arayüzü açıldı"
# 2. CANBOAT kontrolü
which analyzer || { echo "CANBOAT yok, kuruluyor..."; sudo apt install -y canboat; }
# 3. Signal K başlat (systemd servis olarak)
sudo systemctl start signalk.service
sudo systemctl enable signalk.service
# 4. N2K veri akışını test et
timeout 5 candump can0 | analyzer -json | head -20
echo "Gateway hazır — http://$(hostname -I | awk '{print $1}'):3000"
GPS ve pusula entegrasyonu örneği
import subprocess, json, math, os, time
def clear(): os.system('clear')
data = {'lat': None, 'lon': None,
'heading': None, 'sog': None, 'depth': None}
proc = subprocess.Popen(
['bash', '-c', 'candump can0 | analyzer -json'],
stdout=subprocess.PIPE, text=True
)
for line in proc.stdout:
try:
msg = json.loads(line)
pgn = msg.get('pgn')
fields = msg.get('fields', {})
if pgn == 129025:
data['lat'] = fields.get('Latitude')
data['lon'] = fields.get('Longitude')
elif pgn == 127250:
h = fields.get('Heading')
if h: data['heading'] = math.degrees(h)
elif pgn == 129026:
sog = fields.get('SOG')
if sog: data['sog'] = sog * 1.944
elif pgn == 128267:
data['depth'] = fields.get('Depth')
clear()
print("=== NMEA 2000 Dashboard ===")
print(ff"Konum: {data['lat']:.4f}°N {data['lon']:.4f}°E" if data['lat'] else "Konum: ---")
print(ff"Yön: {data['heading']:.1f}°" if data['heading'] else "Yön: ---")
print(ff"SOG: {data['sog']:.1f} kn" if data['sog'] else "SOG: ---")
print(ff"Derinlik: {data['depth']:.1f} m" if data['depth'] else "Derinlik: ---")
except (json.JSONDecodeError, KeyError):
continue
SeaTalk ng ile uyumluluk notu
Raymarine cihazları SeaTalk ng (STng) kullanır. STng elektriksel olarak NMEA 2000 ile uyumludur (aynı CAN fiziksel katman, aynı DeviceNet kablo); ancak özel mavi renk konnektörler ve farklı PGN uzantıları içerir.
| Özellik | NMEA 2000 | SeaTalk ng (STng) |
|---|---|---|
| Elektriksel katman | DeviceNet (Mini) | Aynı (DeviceNet uyumlu) |
| Bitrate | 250 kbit/s | 250 kbit/s |
| Standart PGN'ler | Tam destek | Destekler + özel PGN'ler |
| Konnektör rengi | Gri | Mavi (Raymarine özel) |
| Linux uyumu | Tam | Tam (standart PGN'ler için) |
SeaTalk ng kablolarını NMEA 2000 ağına bağlamak için Raymarine A06045 gibi adaptörler kullanılır. Veya konnektör üretim standartları biliniyorsa elle montaj yapılabilir. Linux ve CANBOAT tarafında herhangi bir özel konfigürasyon gerekmez — standart CAN 250 kbit/s üzerinde çalışır.