NMEA2000-Code weiterbearbeitet

This commit is contained in:
2025-02-07 19:48:32 +01:00
parent a0638c76e5
commit bdbd168123
8 changed files with 259 additions and 72 deletions

View File

@@ -79,11 +79,13 @@ import datetime
import time
import math
import random
#from .lookup import fluidtype
from . import lookup
class BoatValue():
'''
"""
Wert mit Datentyp, Einheit, Validekennzeichen und Skalierungsfaktor
'''
"""
placeholder = '---'
@@ -299,18 +301,23 @@ class BoatValuePressure(BoatValue):
return self.placeholder
class Tank():
"""
Die Instanz beziegt sich auf den Typ. So kann die Instanz 0
für einen Wassertank und einen Dieseltank existieren
"""
def __init__(self, instance=0):
self.instance = instance
self.fluidtype = 1 # water -> lookup
self.volume = None
self.capacity = None
self.instance = instance
self.level = None # percent
self.capacity = None # liter
self.desc = "" # long description
def __str__(self):
out = f" Tank #{self.instance}"
typedesc = lookup.fluidtype[self.fluidtype]
out = f" Tank / {typedesc}: #{self.instance}\n"
out += f" Capacity: {self.capacity} l\n"
out += f" Fluid level: {self.volume} l\n"
out += f" Fluid level: {self.level} %\n"
return out
class Engine():
@@ -505,9 +512,9 @@ class BoatData():
out += f" Longitude: {self.lon.value}\n"
out += f" SOG: {self.sog}\n"
for e in self.engine.values():
print(e)
out += str(e)
for t in self.tank.values():
print(t)
out += str(t)
out += " Satellite info\n"
for s in self.sat.values():
out += str(s)

View File

@@ -1,32 +1,39 @@
"""
NMEA2000-Gerät
- auf dem Bus erkannte Geräte
- für das eigene Gerät steht initUid() zur Verfügung
'''
Platzhalter WIP
- ausprogrammieren nach Bedarf
Geräteliste
- wird regelmäßig aktualisiert
'''
"""
import time
import struct
from . import lookup
class Device():
def __init__(self, address):
#WIP
#Felder können sich noch ändern!
self.address = address
self.instance = 0 # default 0
self.sysinstance = 0 # used with bridged networks, default 0
# WIP: Felder können sich noch ändern!
self.address = address # Kann sich zur Laufzeit ändern
self.lastseen = time.time()
self.uniqueid = None
self.manufacturercode = None
self.lastpinfo = None # Wann letztes Mal Productinfo erhalten?
self.lastcinfo = None # Wann letztes Mal Configurationinfo erhalten?
self.has_cinfo = True # Weitere Abfragen können verhindert werden
# Device info
self.NAME = 0 # Wird über Address-Claim gefüllt
self.uniqueid = None # Z.B. aus der Geräteseriennummer abgeleitet
self.manufacturercode = 2046 # Open Boat Projects
self.instance = 0 # default 0
self.instlower = 0 # 3bit, ISO ECU Instance
self.instupper = 0 # 5bit, ISO Function Instance
self.sysinstance = 0 # used with bridged networks, default 0
self.industrygroup = None
self.name = None # User defined device name
self.product = None # Product name
self.productcode = None # Product code
self.devicefunction = None
self.deviceclass = None
# Product info
self.product = None # Product name
self.productcode = None # Product code
self.serial = None
self.modelvers = None # Hardware Version
self.softvers = None # Current Software Version
@@ -34,30 +41,80 @@ class Device():
self.certlevel = None # NMEA2000 Certification Level
self.loadequiv = None # NMEA2000 LEN
# Configuration Info
# Configuration info
self.instdesc1 = None
self.instdesc2 = None
self.manufinfo = None
def getName(self):
# NAME errechnen aus den Claim-Daten
# TODO Das hier ist noch fehlerhaft!
# Additional data
self.customname = None # User defined device name
def _ISOtime(self, epoch):
if not epoch:
return "n/a"
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(epoch))
def initUid(self):
# Initialize unique id from raspi cpu id and return 21bit value
with open("/sys/firmware/devicetree/base/serial-number", "r") as f:
hexid = f.read().rstrip('\x00')
return int(hexid, 16) & 0x001FFFFF # 21bit mask
def getNAME(self):
"""
NAME is unique on bus
"""
data = bytearray()
data.append((self.deviceclass << 4) | (self.devicefunction & 0x0f))
data.extend(struct.pack('<L', self.uniqueid))
data.extend(struct.pack('<L', self.manufacturercode))
data.extend(struct.pack('<L', self.industrygroup))
data.extend(struct.pack('<I', (self.uniqueid & 0x001fffff) | (self.manufacturercode << 21)))
data.append((self.instlower & 0x07) | ((self.instupper & 0x1f) << 3))
data.append(self.devicefunction)
data.append((self.deviceclass & 0x7f) << 1)
data.append(0x80 | ((self.industrygroup & 0x07) << 4) | (self.sysinstance & 0x0f))
return data
def __str__(self):
out = f"Device: {self.address} : '{self.product}'\n"
out += f" Instance: {self.instance}\n"
out += f" Product Code: {self.productcode}\n"
out += f" Product: {self.product}\n"
out += f" Serial: {self.serial}\n"
out += f" Model Version: {self.modelvers}\n"
out += f" Software Version: {self.softvers}\n"
out += f" NMEA2000 Version: {self.n2kvers}\n"
out += f" Cert-Level: {self.certlevel}\n"
out += f" LEN: {self.loadequiv}"
intNAME = int.from_bytes(self.getNAME())
out = f"Device: {self.address} : '{self.product}'\n"
out += " NAME: {} ({})\n".format(self.getNAME(), intNAME)
out += " last seen: {}\n".format(self._ISOtime(self.lastseen))
out += " Device info\n"
out += f" Unique ID: {self.uniqueid}\n"
out += f" Instance: {self.instance} ({self.instupper}/{self.instlower})\n"
out += f" System instance: {self.sysinstance}\n"
try:
devfnname = lookup.devicefunction[self.deviceclass][self.devicefunction]
except KeyError:
devfnname = "*key error*"
out += f" Device function: {devfnname} ({self.devicefunction})\n"
try:
devclassname = lookup.deviceclass[self.deviceclass]
except KeyError:
devclassname = "*key error*"
out += f" Device class: {devclassname} ({self.deviceclass})\n"
try:
igrpname = lookup.industrygroup[self.industrygroup]
except KeyError:
igrpname = "*key error*"
out += f" Industry group: {igrpname} ({self.industrygroup})\n"
try:
manufname = lookup.manufacturer[self.manufacturercode]
except KeyError:
manufname = "*key error*"
out += f" Manufacturer code: {manufname} ({self.manufacturercode})\n"
out += " Product info at {}\n".format(self._ISOtime(self.lastpinfo))
out += f" Product Code: {self.productcode}\n"
out += f" Product: {self.product}\n"
out += f" Serial: {self.serial}\n"
out += f" Model Version: {self.modelvers}\n"
out += f" Software Version: {self.softvers}\n"
out += f" NMEA2000 Version: {self.n2kvers}\n"
out += f" Cert-Level: {lookup.certlevel[self.certlevel]} ({self.certlevel})\n"
out += f" LEN: {self.loadequiv}\n"
out += " Configuration info at {}\n".format(self._ISOtime(self.lastcinfo))
if self.has_cinfo:
out += f" Installation description 1: {self.instdesc1}\n"
out += f" Installation description 2: {self.instdesc2}\n"
out += f" Manufacturer info: {self.manufinfo}\n"
else:
out += " not available\n"
return out

View File

@@ -146,6 +146,7 @@ control = {
deviceclass = {
0: "Reserved for 2000 Use",
10: "System tools",
11: "WEMA Custom?",
20: "Safety systems",
25: "Internetwork device",
30: "Electrical Distribution",
@@ -169,6 +170,8 @@ devicefunction = { # dependent of deviceclass above
10: {130: "Diagnostic",
140: "Bus Traffic Logger"
},
11: {150: "WEMA Fluid level" # Custom?
},
20: {110: "Alarm Enunciator",
130: "Emergency Positon Indicating Radia Beacon (EPIRB)",
135: "Man Overboard",
@@ -485,7 +488,7 @@ manufacturer = {
1861: "Vector Cantech",
1862: "Yamaha Marine",
1863: "Faria Instruments",
2001: "Open Boat Projects"
2046: "Open Boat Projects"
}
pilotmode = {

View File

@@ -5,8 +5,42 @@ PGNs verarbeiten
'''
import struct
import time
from datetime import timedelta, date
from . import lookup
def parse_126996(buf, source, device):
def parse_60928(buf, device):
"""
Sets data in device and returns the 64bit NAME of device
"""
device.lastseen = time.time()
# 21 bits Unique-ID und 11 bits Manuf.-Code
device.uniqueid = ((buf[0] << 16) + (buf[1] << 8) + buf[2]) >> 3
device.manufacturercode = (buf[3] * 256 + buf[2]) >> 5
device.instance = buf[4]
device.instlower = buf[4] & 0x07
device.instupper = buf[4] >> 3
device.devicefunction = buf[5]
device.deviceclass = (buf[6] & 0x7f) >> 1
device.industrygroup = (buf[7] >> 4) & 0x07 # 3bit
device.sysinstance = buf[7] & 0x0f # 4bit
return struct.unpack_from('>Q', buf, 0)[0]
def parse_126992(buf, source):
# System time
print(f"PGN 126992 System time from {source}")
sid = buf[0]
src = buf[1] & 0x0f
dval = date(1970,1,1) + timedelta(days=(buf[3] << 8) + buf[2])
secs = struct.unpack_from('<L', buf, 4)[0] * 0.0001
print(f" source={source}, date={dval}, secs={secs}, ts={lookup.timesource[src]}")
def parse_126993(buf, device):
# Heartbeat
print(f"Heartbeat from {device.address}")
print(buf)
def parse_126996(buf, device):
# Product information
n2kvers = (buf[0] + buf[1] * 256) / 1000
prodcode = buf[2] + buf[3] * 256
@@ -20,15 +54,15 @@ def parse_126996(buf, source, device):
softvers = softvers.rstrip(b'\xff')
modelvers = modelvers.rstrip(b'\xff')
serial = serial.rstrip(b'\xff')
# Übertragen in die Geräteliste
devices[source].n2kvers = n2kvers
devices[source].productcode = prodcode
devices[source].modelvers = modelvers.decode('ascii').rstrip()
devices[source].softvers = softvers.decode('ascii').rstrip()
devices[source].product = modelid.decode('ascii').rstrip()
devices[source].serial = serial.decode('ascii').rstrip()
devices[source].certlevel = buf[132]
devices[source].loadequiv = buf[133]
# Übertragen in die Gerätedaten
device.n2kvers = n2kvers
device.productcode = prodcode
device.modelvers = modelvers.decode('ascii').rstrip()
device.softvers = softvers.decode('ascii').rstrip()
device.product = modelid.decode('ascii').rstrip()
device.serial = serial.decode('ascii').rstrip()
device.certlevel = buf[132]
device.loadequiv = buf[133]
def parse_126998(buf, source, device):
# Configuration information
@@ -68,9 +102,13 @@ def parse_127257(buf, boatdata):
def parse_127505(buf, boatdata):
# Fluid Level
instance = buf[0] & 0x0f
boatdata.tank[instance].fluidtype = buf[0] >> 4
boatdata.tank[instance].capacity = struct.unpack_from('<L', buf, 2)[0] * 0.1
boatdata.tank[instance].volume = struct.unpack_from('<H', buf, 1)[0] * 0.004
if instance in boatdata.tank:
boatdata.tank[instance].fluidtype = buf[0] >> 4
boatdata.tank[instance].level = struct.unpack_from('<H', buf, 1)[0] * 0.004
boatdata.tank[instance].capacity = struct.unpack_from('<L', buf, 3)[0] * 0.1
print(boatdata.tank[instance])
else:
print(f"Tank {instance} not defined!")
def parse_127508(buf, boatdata):
# Battery status