Started implementing message class, parsing rx and tx pgn list
This commit is contained in:
@@ -3,3 +3,4 @@ __version__ = "0.0.1"
|
||||
from .device import Device
|
||||
from .boatdata import BoatData
|
||||
from .hbuffer import History, HistoryBuffer
|
||||
from .message import Message
|
||||
|
||||
114
device.py
114
device.py
@@ -12,6 +12,24 @@ import time
|
||||
import struct
|
||||
from . import lookup
|
||||
|
||||
def set_to_lines(values, maxlen, indent=0):
|
||||
spaces = ' ' * indent
|
||||
lines = []
|
||||
current = ""
|
||||
for v in values:
|
||||
part = str(v)
|
||||
emax = maxlen - indent
|
||||
if not current:
|
||||
current = part
|
||||
elif len(current) + 1 + len(part) <= emax:
|
||||
current += ',' + part
|
||||
else:
|
||||
lines.append(spaces + current + ',')
|
||||
current = part
|
||||
if current:
|
||||
lines.append(spaces + current)
|
||||
return lines
|
||||
|
||||
class Device():
|
||||
|
||||
def __init__(self, address):
|
||||
@@ -21,6 +39,7 @@ class Device():
|
||||
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
|
||||
self.has_pgnlist = False # PGN-Listen Tx,Rx vorhanden
|
||||
|
||||
# Device info
|
||||
self.NAME = 0 # Wird über getNAME (address claim) gefüllt, 64bit Integer
|
||||
@@ -30,14 +49,13 @@ class Device():
|
||||
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.devicefunction = None
|
||||
self.deviceclass = None
|
||||
|
||||
# Product info
|
||||
self.product = None # Product name
|
||||
self.productcode = None # Product code
|
||||
self.serial = None
|
||||
self.product = None # Product name (ModelID)
|
||||
self.productcode = None # Product code (16bit unsigned)
|
||||
self.serial = None # Device serial number
|
||||
self.modelvers = None # Hardware Version
|
||||
self.softvers = None # Current Software Version
|
||||
self.n2kvers = None # NMEA2000 Network Message Database Version
|
||||
@@ -47,7 +65,11 @@ class Device():
|
||||
# Configuration info
|
||||
self.instdesc1 = None
|
||||
self.instdesc2 = None
|
||||
self.manufinfo = None
|
||||
self.manufinfo = None
|
||||
|
||||
# PGN lists, fill with functions defined below
|
||||
self.pgns_transmit = set()
|
||||
self.pgns_receive = set()
|
||||
|
||||
# Additional data
|
||||
self.customname = None # User defined device name
|
||||
@@ -120,7 +142,7 @@ class Device():
|
||||
print("Ignore collision because of our lower NAME")
|
||||
else:
|
||||
print(f"Answer: DEST={dest}")
|
||||
print(msg.data)
|
||||
print("Data:", msg.data)
|
||||
if dest == self.address:
|
||||
print("We are addressed: WIP!")
|
||||
else:
|
||||
@@ -133,6 +155,84 @@ class Device():
|
||||
print("claim seems ok after 250ms")
|
||||
return claim_ok
|
||||
|
||||
# TODO String handling
|
||||
# Spec says ASCII is only 0x20 to 0x7E. But some devices use full
|
||||
# Latin-1 / ISO-8859-1 code set.
|
||||
# Unicode:
|
||||
# UTF-16 Little Endian
|
||||
# UTF-8 eventually used in some PGNs?
|
||||
def getStringFix(self, text, maxlength=32, filler=b' '):
|
||||
if not text:
|
||||
text = ''
|
||||
if text.isascii():
|
||||
# text.encode('latin1')
|
||||
# text.encode('ascii') can throw errors
|
||||
# text.encode('ascii', 'replace') sets question mark for unknown codes
|
||||
#return b'x01' + bytes(text or '', 'ascii').ljust(maxlength, b' ')
|
||||
return b'x01' + text.encode('ascii', 'ignore').ljust(maxlength, filler)
|
||||
else:
|
||||
# convert to UTF-16 Little Endian
|
||||
return b'x00' + text.encode('utf-16-le').ljust(maxlength, filler)
|
||||
|
||||
def getProductInfo(self):
|
||||
"""
|
||||
Returns data for msg prepared to be sent out as fast packet
|
||||
"""
|
||||
print("Set Productinfo:", self.product)
|
||||
data = bytearray()
|
||||
data.extend(struct.pack('<H', self.n2kvers)) # 2 Byte
|
||||
data.extend(struct.pack('<H', self.productcode)) # 2 Byte
|
||||
data.append(0x01) # String
|
||||
data.extend(bytes(self.product, 'latin1').ljust(32, b' '))
|
||||
#data.extend(getStringFix(self.product))
|
||||
data.append(0x01) # ASCII String
|
||||
data.extend(bytes(self.softvers, 'latin1').ljust(32, b' '))
|
||||
data.append(0x01)
|
||||
data.extend(bytes(self.modelvers, 'latin1').ljust(32, b' '))
|
||||
data.append(0x01)
|
||||
data.extend(bytes(self.serial, 'latin1').ljust(32, b' '))
|
||||
data.append(0x00) # Certification level
|
||||
data.append(0x00) # Load equivalency 0=Not powered by bus
|
||||
return data
|
||||
|
||||
def getConfigInfo(self):
|
||||
"""
|
||||
Configuration info
|
||||
"""
|
||||
print("Set Configinfo")
|
||||
data = bytearray()
|
||||
data.extend(self.getStringFix(self.instdesc1), 32)
|
||||
data.extend(self.getStringFix(self.instdesc2), 32)
|
||||
data.extend(self.getStringFix(self.manufinfo), 32)
|
||||
#data.append(0x01) # ASCII String
|
||||
#data.extend(bytes(self.instdesc1 or '', 'ascii').ljust(32, b' '))
|
||||
#data.append(0x01)
|
||||
#data.extend(bytes(self.instdesc2 or '', 'ascii').ljust(32, b' '))
|
||||
#data.append(0x01)
|
||||
#data.extend(bytes(self.manufinfo or '', 'ascii').ljust(32, b' '))
|
||||
return data
|
||||
|
||||
"""
|
||||
PGN lists (mandatory)
|
||||
59904 ISO Request
|
||||
60928 ISO Address Claim
|
||||
126208 Group Function (Request / Command / Acknowledge)
|
||||
126464 PGN List (Transmit / Receive)
|
||||
126993 Heartbeat
|
||||
126996 Product Information
|
||||
126998 Configuration Information
|
||||
"""
|
||||
def setTxPGNs(self, pgnlist, mandatory=True):
|
||||
self.pgns_transmit = set(pgnlist)
|
||||
if mandatory:
|
||||
self.pgns_transmit.add((60928, 126208, 126464, 126996))
|
||||
|
||||
def setRxPGNs(self, pgnlist, mandatory=True):
|
||||
self.pgns_receive = set(pgnlist)
|
||||
if mandatory:
|
||||
self.pgns_receive.add((59904, 60928, 126208, 126996, 126998))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
out = f"Device: {self.address} : '{self.product}'\n"
|
||||
out += " NAME: {} ({})\n".format(self.getNAME(), self.NAME)
|
||||
@@ -177,4 +277,6 @@ class Device():
|
||||
out += f" Manufacturer info: {self.manufinfo}\n"
|
||||
else:
|
||||
out += " not available\n"
|
||||
out += " PGNs RX: \n{}\n".format('\n'.join(set_to_lines(self.pgns_receive, 72, 4)) or 'n/a')
|
||||
out += " PGNs TX: \n{}\n".format('\n'.join(set_to_lines(self.pgns_transmit, 72, 4)) or 'n/a')
|
||||
return out
|
||||
|
||||
106
message.py
Normal file
106
message.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
NMEA2000 Nachricht
|
||||
|
||||
"""
|
||||
|
||||
import can
|
||||
import struct
|
||||
from math import ceil
|
||||
from .pgntype import pgntype
|
||||
|
||||
class Message():
|
||||
|
||||
def __init__(self, bus, source, data=None):
|
||||
self._bus = bus
|
||||
self._source = source
|
||||
self._priority = 5
|
||||
if data:
|
||||
print("msg: set data: ", type(data))
|
||||
self._data = data
|
||||
else:
|
||||
self._data = bytearray()
|
||||
self.pgn = None
|
||||
#self.sequence = 0xff
|
||||
self.sequence = 0
|
||||
|
||||
def set_pgn(self, pgn):
|
||||
self.pgn = pgn
|
||||
|
||||
def set_priority(self, priority):
|
||||
self._priority = priority
|
||||
|
||||
def get_fast_frames(self, rawdata, sc):
|
||||
# Erstelle ein Liste von Frames aufgrund vorliegender Rohdaten
|
||||
# zum transferieren von bis zu 223 Bytes über eine fast-Message
|
||||
# Der sequence-counter muß extern inkrementiert werden, er hat
|
||||
# 3 Bit Länge. Der Framecounter fc hat eine Länge von 5 Bit.
|
||||
# Der erste Frame kann 6 Bytes aufnehmen
|
||||
# Alle weiteren Frames jeweils 7 Bytes
|
||||
datalen = len(rawdata)
|
||||
if datalen > 223:
|
||||
raise ValueError("data for fast packet too long")
|
||||
if datalen < 7:
|
||||
raise ValueError("data for fast packet too short")
|
||||
nf = ceil((datalen - 6) / 7 + 1) # number of frames
|
||||
fc = 0 # frame counter, nibble 2
|
||||
frames = list()
|
||||
# Frame 1
|
||||
data = bytearray()
|
||||
data.append((sc << 5) + fc)
|
||||
data.append(datalen)
|
||||
for b in rawdata[:6]:
|
||||
data.append(b)
|
||||
frames.append(data)
|
||||
nf -= 1
|
||||
# Frame 2..n
|
||||
p = 6
|
||||
while nf > 0:
|
||||
data = bytearray()
|
||||
fc += 1
|
||||
data.append((sc << 5) + fc)
|
||||
for b in rawdata[p:p+7]:
|
||||
data.append(b)
|
||||
frames.append(data)
|
||||
p += 7
|
||||
nf -= 1
|
||||
print("message:get_fast_frames")
|
||||
print(frames)
|
||||
return frames
|
||||
|
||||
def send_single(self):
|
||||
msg = can.Message(
|
||||
arbitration_id = (((self._priority << 18) + self.pgn) << 8) + self._source,
|
||||
data = self._data,
|
||||
is_extended_id = True
|
||||
);
|
||||
try:
|
||||
self._bus.send(msg)
|
||||
except can.CanError:
|
||||
print(f"Message {self.pgn} NOT sent")
|
||||
return False
|
||||
return True
|
||||
|
||||
def send_fast(self):
|
||||
id = (((self._priority << 18) + self.pgn) << 8) + self._source
|
||||
try:
|
||||
for frame in self.get_fast_frames(self._data, self.sequence):
|
||||
msg = can.Message(
|
||||
arbitration_id = id,
|
||||
data = frame,
|
||||
is_extended_id = True
|
||||
);
|
||||
self._bus.send(msg)
|
||||
except can.CanError:
|
||||
print(f"Message {self.pgn} NOT sent")
|
||||
return False
|
||||
finally:
|
||||
self.sequence += 1
|
||||
self.sequence %= 8
|
||||
print("send fast: adjusted sequence {}".format(self.sequence))
|
||||
return True
|
||||
|
||||
def send(self):
|
||||
if pgntype(self.pgn) == "S":
|
||||
self.send_single()
|
||||
else:
|
||||
self.send_fast()
|
||||
17
parser.py
17
parser.py
@@ -26,6 +26,22 @@ def parse_60928(buf, device):
|
||||
device.sysinstance = buf[7] & 0x0f # 4bit
|
||||
return struct.unpack_from('>Q', buf, 0)[0]
|
||||
|
||||
def parse_126464(buf, device):
|
||||
# PGN Lists: tx and rx
|
||||
# Byte 1: 0 = Transmit PGN list
|
||||
# 1 = Receive PGN list
|
||||
functioncode = buf[0]
|
||||
# set of 24bit PGNs (little endian)
|
||||
values = { buf[i] | (buf[i+1] << 8) | (buf[i+2] << 16)
|
||||
for i in range(1, len(buf)-4, 3) }
|
||||
if functioncode == 0:
|
||||
device.pgns_transmit = values
|
||||
elif functioncode == 1:
|
||||
device.pgns_receive = values
|
||||
else:
|
||||
return
|
||||
device.has_pgnlist = True
|
||||
|
||||
def parse_126983(buf, source):
|
||||
# Alert
|
||||
# PGN 126984 is response
|
||||
@@ -93,7 +109,6 @@ def parse_126992(buf, source):
|
||||
def parse_126993(buf, device):
|
||||
# Heartbeat
|
||||
print(f"Heartbeat from {device.address}")
|
||||
print(buf)
|
||||
|
||||
def parse_126996(buf, device):
|
||||
# Product information
|
||||
|
||||
Reference in New Issue
Block a user